]> Shamusworld >> Repos - architektonas/blobdiff - src/fileio.cpp
Changes to make containers behave like a first-class object.
[architektonas] / src / fileio.cpp
index 7cd6816d4766f63690d14d7ce0dbca13c7bc31b3..ace5bfa247713a3adc4aabee61f0dc3871a4c0aa 100644 (file)
 // JLH = James Hammons <jlhamm@acm.org>
 //
 // Who  When        What
-// ---  ----------  -------------------------------------------------------------
+// ---  ----------  ------------------------------------------------------------
 // JLH  02/20/2013  Created this file
 //
 
 #include "fileio.h"
 
 //#include <stdio.h>
+#include <errno.h>
 #include <stdlib.h>
-#include "container.h"
+#include <string.h>
+#include <vector>
+#include "applicationwindow.h"
+#include "drawingview.h"
+#include "global.h"
+#include "structs.h"
 
+/*
+How to handle connected objects
+-------------------------------
 
-/*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * object)
+Every Object has a vector<Object *> which enumerates all Objects connected to
+the one we're looking at. So it looks like we'll have to take a two pass
+approach to loading and saving.
+
+Basically, in the saving case, first we write out all objects, keeping a
+pointer-to-index-number record. Second, we loop through all the objects we
+wrote out, writing connection lists. Format (indices are 1-based):
+
+CONNECTIONS
+1: 12 3
+3: 1
+12: 1
+ENDCONNECTIONS
+
+In the reading case, we do pretty much the same: we construct pointer-to-index-
+number list, then read the connection list. The PTIN connects the index to the
+Object pointers we've created. Then we simply call the Object's Connect()
+function to connect the objects.
+
+Small problem though: How does a Dimension know which points on a Line it's
+connected to? This approach tells the Dimension it's connected to the Line and
+the Line that it's connected to the Dimension, but not which points.
+
+How to handle them then? Do we list the point with the Object pointed at? A
+Line can contain an infinite number of points to connect with besides its
+endpoints.
+
+So with each connection Object in the vector, there would also have to be a
+corresponding point to go with it, that would be gotten from the other Object's
+Connect() function. Or, instead of a point, a parameter value?
+
+Doing that, with the Line and a parameter "t", if t == 0 we have endpoint 1.
+if t == 1, then we have endpoint 2. With a Circle, the parameter is a number
+between 0 and 1 (scaled to 0 to 2π). With an Arc, the parameter goes from 0 to
+1, 0 being enpoint 1 and 1 being endpoint 2.
+
+How does this work for moving objects that are connected? Again, with the Line
+and Dimension. The Line's connections looks like this:
+
+Object *: dim1, t = 0
+Object *: dim1, t = 1
+
+Dimension looks like this:
+
+Object *: line1, t = 0
+Object *: line1, t = 1
+
+For Dimensions, it can query the connected object (if any) using something like
+GetPointForParameter(). That way it can figure out where its endpoints are. If
+there is no connected point, then it uses its internal point.
+
+
+Dimensions are special cases of lines: They have exactly *two* points and none
+in between. Therefore, the Dimension object only needs to have two points. But
+those points can be connected to multiple objects. The can also be connected to
+no points/Objects too.
+
+How to describe them and their connections (or lack thereof)?
+
+Would have to be a 2nd pass, after all objects have been written out in order.
+Then you could do something like:
+
+DIMCONNECTIONS
+8 (the Dimension #): 1 (the Object # for point 1) 1 (the Object # for point 2)
+ENDDIMCONNECTIONS
+
+
+
+Connection attributes: E.g., between a Line a Circle, it can be tangent,
+perpendicular, or an arbitrary angle. How to encode that information? It's not
+intrinsic to either the Line or the Circle, but is a function of the
+relationship between them by virtue of their connection.
+
+
+OTHER CONSIDERATIONS:
+---------------------
+
+  - Need to figure out how to store the Layer list (should layer list be optional?)
+  - Need to figure out how to store the Block list and blocks
+
+
+*/
+
+//enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFLine, OTFCircle, OTFArc,
+//     OTFDimension, OTFPolygon, OTFText, OTFImage, OTFBlock, OTFEndOfFile };
+enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFObject, OTFEndOfFile };
+
+
+// Instantiate class variables
+/*static*/ int FileIO::objectFileType = OTFObject;
+
+
+/*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * c)
+{
+       /* Approach: loop through the container, doing a depth-first traversal. Any
+          extra containers found are looped through until there aren't any more
+          down, then ordinary objects are described. This can be handled by a
+          virtual Object function that reports the object by itself if it's a non-
+          Container, otherwise it enumerates all objects within itself. */
+
+       fprintf(file, "ARCHITEKTONAS DRAWING V1.2\n");
+       fprintf(file, "LAYERS %i\n", Global::numLayers);
+
+       for(int i=0; i<Global::numLayers; i++)
+               fprintf(file, "%i %i \"%s\"\n", (Global::layerHidden[i] ? 1 : 0), (Global::layerLocked[i] ? 1 : 0), Global::layerName[i].c_str());
+
+       fprintf(file, "ACTIVE %i\n", Global::activeLayer);
+       WriteObjectToFile(file, (Object *)c);
+       fprintf(file, "END\n");
+       return true;
+}
+
+
+/*static*/ bool FileIO::LoadAtnsFile(FILE * file, Container * drawing)
+{
+       float version;
+
+       fscanf(file, "ARCHITEKTONAS DRAWING V%f\n", &version);
+
+       // Clear out layer vectors
+       Global::layerHidden.clear();
+       Global::layerLocked.clear();
+       Global::layerName.clear();
+
+       if (version == 1.0f)
+               return LoadVersion1_0(file, drawing);
+       else if (version == 1.1f)
+               return LoadVersion1_1(file, drawing);
+       else if (version == 1.2f)
+               return LoadVersion1_2(file, drawing);
+
+//printf("LoadAtnsFile: Could not locate version! (version=%f)\n", version);
+       return false;
+}
+
+
+/*static*/ void FileIO::ResetLayerVectors(void)
+{
+       // Set up layer vectors
+       Global::layerHidden.insert(Global::layerHidden.begin(), false);
+       Global::layerLocked.insert(Global::layerLocked.begin(), false);
+       Global::layerName.insert(Global::layerName.begin(), "Background");
+       Global::numLayers = 1;
+       Global::activeLayer = 0;
+}
+
+
+/*static*/ bool FileIO::LoadVersion1_0(FILE * file, Container * drawing)
 {
-//     QString filename2 = QFileDialog::getSaveFileName(NULL, tr("Save Drawing"),
-//             "", tr("Architektonas files (*.drawing)"));
+       // Approach: read each object in the file, one by one. If the object is a
+       // Container, add objects to it until an "endContainer" marker is found.
+       // This will require a stack to maintain the current Container.
+       std::vector<Container *> containerStack;
+       Container * currentTopContainer = drawing;
+       ResetLayerVectors();
+
+       while (!feof(file))
+       {
+               // Reconstruct the object
+               Object * obj = GetObjectFromFile(file);
+
+               // objectFileType is set in GetObjectFromFile()...
+               if (objectFileType == OTFObject)
+               {
+                       if (obj == NULL)
+                               return false;
+
+                       currentTopContainer->objects.push_back(obj);
+//printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
+
+                       // If the object is a container, push current TLC on the stack and
+                       // set it as the new TLC
+                       if (obj->type == OTContainer)
+                       {
+                               containerStack.push_back(currentTopContainer);
+                               currentTopContainer = (Container *)obj;
+                       }
+               }
+               else if (objectFileType == OTFContainerEnd)
+               {
+                       // Container is done, so pop the stack to get back the previous TLC
+                       currentTopContainer = containerStack.back();
+                       containerStack.pop_back();
+               }
+               else if (objectFileType == OTFEndOfFile)
+               {
+//printf("Load: container size = %li\n", drawing->objects.size());
+                       return true;
+               }
+       }
 
        return false;
 }
 
 
-/*static*/ bool FileIO::LoadAtnsFile(FILE * file, Container * object)
+/*static*/ bool FileIO::LoadVersion1_1(FILE * file, Container * drawing)
 {
-//     QString filename2 = QFileDialog::getOpenFileName(NULL, tr("Open Drawing"),
-//             "", tr("Architektonas files (*.drawing)"));
+       // Approach: read each object in the file, one by one. If the object is a
+       // Container, add objects to it until an "endContainer" marker is found.
+       // This will require a stack to maintain the current Container.
+       std::vector<Container *> containerStack;
+       Container * currentTopContainer = drawing;
+       ResetLayerVectors();
+
+       while (!feof(file))
+       {
+               // Reconstruct the object (extended format!)
+               Object * obj = GetObjectFromFile(file, true);
+
+               // objectFileType is set in GetObjectFromFile()...
+               if (objectFileType == OTFObject)
+               {
+                       if (obj == NULL)
+                               return false;
+
+                       currentTopContainer->objects.push_back(obj);
+//printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
+
+                       // If the object is a container, push current TLC on the stack and
+                       // set it as the new TLC
+                       if (obj->type == OTContainer)
+                       {
+                               containerStack.push_back(currentTopContainer);
+                               currentTopContainer = (Container *)obj;
+                       }
+               }
+               else if (objectFileType == OTFContainerEnd)
+               {
+                       // Add the extents of the current container
+                       Rect r = ApplicationWindow::drawing->GetObjectExtents((Object *)currentTopContainer);
+                       currentTopContainer->p[0] = r.TopLeft();
+                       currentTopContainer->p[1] = r.BottomRight();
+//printf("Container extents: <%lf, %lf>, <%lf, %lf>\n", r.l, r.t, r.r, r.b);
+
+                       // Container is done, so pop the stack to get back the previous TLC
+                       currentTopContainer = containerStack.back();
+                       containerStack.pop_back();
+               }
+               else if (objectFileType == OTFEndOfFile)
+               {
+//printf("Load: container size = %li\n", drawing->objects.size());
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+
+/*static*/ bool FileIO::LoadVersion1_2(FILE * file, Container * drawing)
+{
+       int hidden, locked;
+       char textBuffer[65536];
+
+       // Load layer information first
+       fscanf(file, "LAYERS %i\n", &Global::numLayers);
+
+       for(int i=0; i<Global::numLayers; i++)
+       {
+               fscanf(file, "%i %i \"%[^\"]\"\n", &hidden, &locked, textBuffer);
+               Global::layerHidden.push_back(hidden ? true : false);
+               Global::layerLocked.push_back(locked ? true : false);
+               Global::layerName.push_back(textBuffer);
+       }
+
+       fscanf(file, "ACTIVE %i\n", &Global::activeLayer);
+
+       // Approach: read each object in the file, one by one. If the object is a
+       // Container, add objects to it until an "endContainer" marker is found.
+       // This will require a stack to maintain the current Container.
+       std::vector<Container *> containerStack;
+       Container * currentTopContainer = drawing;
+
+       while (!feof(file))
+       {
+               // Reconstruct the object (extended format!)
+               Object * obj = GetObjectFromFile(file, true);
+
+               // objectFileType is set in GetObjectFromFile()...
+               if (objectFileType == OTFObject)
+               {
+                       if (obj == NULL)
+                               return false;
+
+                       currentTopContainer->objects.push_back(obj);
+//printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
+
+                       // If the object is a container, push current TLC on the stack and
+                       // set it as the new TLC
+                       if (obj->type == OTContainer)
+                       {
+                               containerStack.push_back(currentTopContainer);
+                               currentTopContainer = (Container *)obj;
+                       }
+               }
+               else if (objectFileType == OTFContainerEnd)
+               {
+                       // Add the extents of the current container
+                       Rect r = ApplicationWindow::drawing->GetObjectExtents((Object *)currentTopContainer);
+                       currentTopContainer->p[0] = r.TopLeft();
+                       currentTopContainer->p[1] = r.BottomRight();
+//printf("Container extents: <%lf, %lf>, <%lf, %lf>\n", r.l, r.t, r.r, r.b);
+
+                       // Container is done, so pop the stack to get back the previous TLC
+                       currentTopContainer = containerStack.back();
+                       containerStack.pop_back();
+               }
+               else if (objectFileType == OTFEndOfFile)
+               {
+//printf("Load: container size = %li\n", drawing->objects.size());
+                       return true;
+               }
+       }
 
        return false;
 }
 
 
+/*static*/ Object * FileIO::GetObjectFromFile(FILE * file, bool extended/*= false*/)
+{
+       char buffer[256];
+       char textBuffer[65536];
+       int foundLayer = 0;
+       /*int num =*/ fscanf(file, "%s", buffer);
+       Object * obj = NULL;
+       objectFileType = OTFObject;
+//printf("FileIO: fscanf returned %i, buffer = \"%s\"\n", num, buffer);
+
+// The following fugliness is for troubleshooting. Can remove later.
+       if ((strcmp(buffer, "END") != 0) && (strcmp(buffer, "ENDCONTAINER") != 0))
+{
+errno = 0;
+               /*num =*/ fscanf(file, " %i ", &foundLayer);
+//printf("FileIO: fscanf returned %i, foundLayer = %i\n", num, foundLayer);
+if (errno)
+{
+       if (errno == EAGAIN)
+               printf("EAGAIN\n");
+       else if (errno == EBADF)
+               printf("EBADF\n");
+       else if (errno == EILSEQ)
+               printf("EILSEQ\n");
+       else if (errno == EINTR)
+               printf("EINTR\n");
+       else if (errno == EINVAL)
+               printf("EINVAL\n");
+       else if (errno == ENOMEM)
+               printf("ENOMEM\n");
+       else if (errno == ERANGE)
+               printf("ERANGE\n");
+       else
+               printf("errno = %i\n", errno);
+}
+}
+
+       // Need to add pen attributes as well... do that for v1.1 :-P
+       // And fill attributes... do that for v1.3 (1.2 added layers)
+       if (strcmp(buffer, "LINE") == 0)
+       {
+               Point p1, p2;
+               fscanf(file, "(%lf,%lf) (%lf,%lf)", &p1.x, &p1.y, &p2.x, &p2.y);
+               obj = (Object *)new Line(p1, p2);
+       }
+       else if (strcmp(buffer, "CIRCLE") == 0)
+       {
+               Point p;
+               double r;
+               fscanf(file, "(%lf,%lf) %lf", &p.x, &p.y, &r);
+               obj = (Object *)new Circle(p, r);
+       }
+       else if (strcmp(buffer, "ARC") == 0)
+       {
+               Point p;
+               double r, a1, a2;
+               fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &p.x, &p.y, &r, &a1, &a2);
+               obj = (Object *)new Arc(p, r, a1, a2);
+       }
+       else if (strcmp(buffer, "TEXT") == 0)
+       {
+               Point p;
+               fscanf(file, "(%lf,%lf) \"%[^\"]\"", &p.x, &p.y, textBuffer);
+               obj = (Object *)new Text(p, textBuffer);
+       }
+       else if (strcmp(buffer, "DIMENSION") == 0)
+       {
+               Point p1, p2;
+               DimensionType type;
+               fscanf(file, "(%lf,%lf) (%lf,%lf) %i", &p1.x, &p1.y, &p2.x, &p2.y, (int *)&type);
+               obj = (Object *)new Dimension(p1, p2, type);
+       }
+       else if (strcmp(buffer, "CONTAINER") == 0)
+       {
+               obj = (Object *)new Container();
+//             objectFileType = OTFContainer;
+       }
+       else if (strcmp(buffer, "ENDCONTAINER") == 0)
+       {
+               objectFileType = OTFContainerEnd;
+       }
+       else if (strcmp(buffer, "END") == 0)
+       {
+               objectFileType = OTFEndOfFile;
+       }
+       else
+               printf("Unknown object type '%s'...\n", buffer);
+
+       if (obj != NULL)
+       {
+               obj->layer = foundLayer;
+
+               if (extended && (obj->type != OTContainer))
+               {
+                       fscanf(file, " (%i, %f, %i)", &obj->color, &obj->thickness, &obj->style);
+               }
+       }
+
+       return obj;
+}
+
+
+/*static*/ bool FileIO::WriteObjectToFile(FILE * file, Object * obj)
+{
+       // Sanity check
+       if (obj == NULL)
+               return false;
+
+       switch (obj->type)
+       {
+       case OTLine:
+               fprintf(file, "LINE %i (%lf,%lf) (%lf,%lf)", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y);
+               break;
+       case OTCircle:
+               fprintf(file, "CIRCLE %i (%lf,%lf) %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]);
+               break;
+       case OTEllipse:
+               break;
+       case OTArc:
+               fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0], obj->angle[0], obj->angle[1]);
+               break;
+       case OTPolygon:
+               break;
+       case OTDimension:
+//             fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, dimensionType);
+               fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y, ((Dimension *)obj)->subtype);
+               break;
+       case OTSpline:
+               break;
+       case OTText:
+               fprintf(file, "TEXT %i (%lf,%lf) \"%s\"", obj->layer, obj->p[0].x, obj->p[0].y, ((Text *)obj)->s.c_str());
+               break;
+       case OTContainer:
+       {
+               Container * c = (Container *)obj;
+
+               if (c->topLevel == false)
+                       fprintf(file, "CONTAINER %i\n", obj->layer);
+
+               std::vector<void *>::iterator i;
+
+               for(i=c->objects.begin(); i!=c->objects.end(); i++)
+                       WriteObjectToFile(file, (Object *)*i);
+
+               if (c->topLevel == false)
+                       fprintf(file, "ENDCONTAINER\n");
+
+               break;
+       }
+       default:
+               break;
+       }
+
+       if (obj->type != OTContainer)
+               fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style);
+
+       return true;
+}
+