2 // fileio.cpp: Architektonas file save/load support
4 // Part of the Architektonas Project
5 // (C) 2013 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
8 // JLH = James Hammons <jlhamm@acm.org>
11 // --- ---------- ------------------------------------------------------------
12 // JLH 02/20/2013 Created this file
22 #include "applicationwindow.h"
23 #include "drawingview.h"
28 How to handle connected objects
29 -------------------------------
31 Every Object has a vector<Object *> which enumerates all Objects connected to
32 the one we're looking at. So it looks like we'll have to take a two pass
33 approach to loading and saving.
35 Basically, in the saving case, first we write out all objects, keeping a
36 pointer-to-index-number record. Second, we loop through all the objects we
37 wrote out, writing connection lists. Format (indices are 1-based):
45 In the reading case, we do pretty much the same: we construct pointer-to-index-
46 number list, then read the connection list. The PTIN connects the index to the
47 Object pointers we've created. Then we simply call the Object's Connect()
48 function to connect the objects.
50 Small problem though: How does a Dimension know which points on a Line it's
51 connected to? This approach tells the Dimension it's connected to the Line and
52 the Line that it's connected to the Dimension, but not which points.
54 How to handle them then? Do we list the point with the Object pointed at? A
55 Line can contain an infinite number of points to connect with besides its
58 So with each connection Object in the vector, there would also have to be a
59 corresponding point to go with it, that would be gotten from the other Object's
60 Connect() function. Or, instead of a point, a parameter value?
62 Doing that, with the Line and a parameter "t", if t == 0 we have endpoint 1.
63 if t == 1, then we have endpoint 2. With a Circle, the parameter is a number
64 between 0 and 1 (scaled to 0 to 2π). With an Arc, the parameter goes from 0 to
65 1, 0 being enpoint 1 and 1 being endpoint 2.
67 How does this work for moving objects that are connected? Again, with the Line
68 and Dimension. The Line's connections looks like this:
73 Dimension looks like this:
75 Object *: line1, t = 0
76 Object *: line1, t = 1
78 For Dimensions, it can query the connected object (if any) using something like
79 GetPointForParameter(). That way it can figure out where its endpoints are. If
80 there is no connected point, then it uses its internal point.
83 Dimensions are special cases of lines: They have exactly *two* points and none
84 in between. Therefore, the Dimension object only needs to have two points. But
85 those points can be connected to multiple objects. The can also be connected to
86 no points/Objects too.
88 How to describe them and their connections (or lack thereof)?
90 Would have to be a 2nd pass, after all objects have been written out in order.
91 Then you could do something like:
94 8 (the Dimension #): 1 (the Object # for point 1) 1 (the Object # for point 2)
99 Connection attributes: E.g., between a Line a Circle, it can be tangent,
100 perpendicular, or an arbitrary angle. How to encode that information? It's not
101 intrinsic to either the Line or the Circle, but is a function of the
102 relationship between them by virtue of their connection.
105 OTHER CONSIDERATIONS:
106 ---------------------
108 - Need to figure out how to store the Layer list (should layer list be optional?)
109 - Need to figure out how to store the Block list and blocks
114 //enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFLine, OTFCircle, OTFArc,
115 // OTFDimension, OTFPolygon, OTFText, OTFImage, OTFBlock, OTFEndOfFile };
116 enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFObject, OTFEndOfFile };
119 // Instantiate class variables
120 /*static*/ int FileIO::objectFileType = OTFObject;
123 /*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * c)
125 /* Approach: loop through the container, doing a depth-first traversal. Any
126 extra containers found are looped through until there aren't any more
127 down, then ordinary objects are described. This can be handled by a
128 virtual Object function that reports the object by itself if it's a non-
129 Container, otherwise it enumerates all objects within itself. */
131 fprintf(file, "ARCHITEKTONAS DRAWING V1.2\n");
132 fprintf(file, "LAYERS %i\n", Global::numLayers);
134 for(int i=0; i<Global::numLayers; i++)
135 fprintf(file, "%i %i \"%s\"\n", (Global::layerHidden[i] ? 1 : 0), (Global::layerLocked[i] ? 1 : 0), Global::layerName[i].c_str());
137 fprintf(file, "ACTIVE %i\n", Global::activeLayer);
138 WriteObjectToFile(file, (Object *)c);
139 fprintf(file, "END\n");
144 /*static*/ bool FileIO::LoadAtnsFile(FILE * file, Container * drawing)
148 fscanf(file, "ARCHITEKTONAS DRAWING V%f\n", &version);
150 // Clear out layer vectors
151 Global::layerHidden.clear();
152 Global::layerLocked.clear();
153 Global::layerName.clear();
156 return LoadVersion1_0(file, drawing);
157 else if (version == 1.1f)
158 return LoadVersion1_1(file, drawing);
159 else if (version == 1.2f)
160 return LoadVersion1_2(file, drawing);
162 //printf("LoadAtnsFile: Could not locate version! (version=%f)\n", version);
167 /*static*/ void FileIO::ResetLayerVectors(void)
169 // Set up layer vectors
170 Global::layerHidden.insert(Global::layerHidden.begin(), false);
171 Global::layerLocked.insert(Global::layerLocked.begin(), false);
172 Global::layerName.insert(Global::layerName.begin(), "Background");
173 Global::numLayers = 1;
174 Global::activeLayer = 0;
178 /*static*/ bool FileIO::LoadVersion1_0(FILE * file, Container * drawing)
180 // Approach: read each object in the file, one by one. If the object is a
181 // Container, add objects to it until an "endContainer" marker is found.
182 // This will require a stack to maintain the current Container.
183 std::vector<Container *> containerStack;
184 Container * currentTopContainer = drawing;
189 // Reconstruct the object
190 Object * obj = GetObjectFromFile(file);
192 // objectFileType is set in GetObjectFromFile()...
193 if (objectFileType == OTFObject)
198 currentTopContainer->objects.push_back(obj);
199 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
201 // If the object is a container, push current TLC on the stack and
202 // set it as the new TLC
203 if (obj->type == OTContainer)
205 containerStack.push_back(currentTopContainer);
206 currentTopContainer = (Container *)obj;
209 else if (objectFileType == OTFContainerEnd)
211 // Container is done, so pop the stack to get back the previous TLC
212 currentTopContainer = containerStack.back();
213 containerStack.pop_back();
215 else if (objectFileType == OTFEndOfFile)
217 //printf("Load: container size = %li\n", drawing->objects.size());
226 /*static*/ bool FileIO::LoadVersion1_1(FILE * file, Container * drawing)
228 // Approach: read each object in the file, one by one. If the object is a
229 // Container, add objects to it until an "endContainer" marker is found.
230 // This will require a stack to maintain the current Container.
231 std::vector<Container *> containerStack;
232 Container * currentTopContainer = drawing;
237 // Reconstruct the object (extended format!)
238 Object * obj = GetObjectFromFile(file, true);
240 // objectFileType is set in GetObjectFromFile()...
241 if (objectFileType == OTFObject)
246 currentTopContainer->objects.push_back(obj);
247 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
249 // If the object is a container, push current TLC on the stack and
250 // set it as the new TLC
251 if (obj->type == OTContainer)
253 containerStack.push_back(currentTopContainer);
254 currentTopContainer = (Container *)obj;
257 else if (objectFileType == OTFContainerEnd)
259 // Add the extents of the current container
260 Rect r = ApplicationWindow::drawing->GetObjectExtents((Object *)currentTopContainer);
261 currentTopContainer->p[0] = r.TopLeft();
262 currentTopContainer->p[1] = r.BottomRight();
263 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\n", r.l, r.t, r.r, r.b);
265 // Container is done, so pop the stack to get back the previous TLC
266 currentTopContainer = containerStack.back();
267 containerStack.pop_back();
269 else if (objectFileType == OTFEndOfFile)
271 //printf("Load: container size = %li\n", drawing->objects.size());
280 /*static*/ bool FileIO::LoadVersion1_2(FILE * file, Container * drawing)
283 char textBuffer[65536];
285 // Load layer information first
286 fscanf(file, "LAYERS %i\n", &Global::numLayers);
288 for(int i=0; i<Global::numLayers; i++)
290 fscanf(file, "%i %i \"%[^\"]\"\n", &hidden, &locked, textBuffer);
291 Global::layerHidden.push_back(hidden ? true : false);
292 Global::layerLocked.push_back(locked ? true : false);
293 Global::layerName.push_back(textBuffer);
296 fscanf(file, "ACTIVE %i\n", &Global::activeLayer);
298 // Approach: read each object in the file, one by one. If the object is a
299 // Container, add objects to it until an "endContainer" marker is found.
300 // This will require a stack to maintain the current Container.
301 std::vector<Container *> containerStack;
302 Container * currentTopContainer = drawing;
306 // Reconstruct the object (extended format!)
307 Object * obj = GetObjectFromFile(file, true, true);
309 // objectFileType is set in GetObjectFromFile()...
310 if (objectFileType == OTFObject)
315 currentTopContainer->objects.push_back(obj);
316 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
318 // If the object is a container, push current TLC on the stack and
319 // set it as the new TLC
320 if (obj->type == OTContainer)
322 containerStack.push_back(currentTopContainer);
323 currentTopContainer = (Container *)obj;
326 else if (objectFileType == OTFContainerEnd)
328 // Add the extents of the current container
329 Rect r = ApplicationWindow::drawing->GetObjectExtents((Object *)currentTopContainer);
330 currentTopContainer->p[0] = r.TopLeft();
331 currentTopContainer->p[1] = r.BottomRight();
332 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\n", r.l, r.t, r.r, r.b);
334 // Container is done, so pop the stack to get back the previous TLC
335 currentTopContainer = containerStack.back();
336 containerStack.pop_back();
338 else if (objectFileType == OTFEndOfFile)
340 //printf("Load: container size = %li\n", drawing->objects.size());
349 /*static*/ Object * FileIO::GetObjectFromFile(FILE * file, bool extended/*= false*/, bool ext2/*= false*/)
352 char textBuffer[65536];
354 /*int num =*/ fscanf(file, "%s", buffer);
356 objectFileType = OTFObject;
357 //printf("FileIO: fscanf returned %i, buffer = \"%s\"\n", num, buffer);
359 // The following fugliness is for troubleshooting. Can remove later.
360 if ((strcmp(buffer, "END") != 0) && (strcmp(buffer, "ENDCONTAINER") != 0))
363 /*num =*/ fscanf(file, " %i ", &foundLayer);
364 //printf("FileIO: fscanf returned %i, foundLayer = %i\n", num, foundLayer);
369 else if (errno == EBADF)
371 else if (errno == EILSEQ)
373 else if (errno == EINTR)
375 else if (errno == EINVAL)
377 else if (errno == ENOMEM)
379 else if (errno == ERANGE)
382 printf("errno = %i\n", errno);
386 // Need to add pen attributes as well... do that for v1.1 :-P
387 // And fill attributes... do that for v1.3 (1.2 added layers)
388 if (strcmp(buffer, "LINE") == 0)
391 fscanf(file, "(%lf,%lf) (%lf,%lf)", &p1.x, &p1.y, &p2.x, &p2.y);
392 obj = (Object *)new Line(p1, p2);
394 else if (strcmp(buffer, "CIRCLE") == 0)
398 fscanf(file, "(%lf,%lf) %lf", &p.x, &p.y, &r);
399 obj = (Object *)new Circle(p, r);
401 else if (strcmp(buffer, "ARC") == 0)
405 fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &p.x, &p.y, &r, &a1, &a2);
406 obj = (Object *)new Arc(p, r, a1, a2);
408 else if (strcmp(buffer, "TEXT") == 0)
411 fscanf(file, "(%lf,%lf) \"%[^\"]\"", &p.x, &p.y, textBuffer);
412 obj = (Object *)new Text(p, textBuffer);
414 else if (strcmp(buffer, "DIMENSION") == 0)
419 fscanf(file, "(%lf,%lf) (%lf,%lf) %i", &p1.x, &p1.y, &p2.x, &p2.y, (int *)&type);
422 fscanf(file, " %lf", &offset);
424 obj = (Object *)new Dimension(p1, p2, type, offset);
426 else if (strcmp(buffer, "CONTAINER") == 0)
428 obj = (Object *)new Container();
429 // objectFileType = OTFContainer;
431 else if (strcmp(buffer, "ENDCONTAINER") == 0)
433 objectFileType = OTFContainerEnd;
435 else if (strcmp(buffer, "END") == 0)
437 objectFileType = OTFEndOfFile;
440 printf("Unknown object type '%s'...\n", buffer);
444 obj->layer = foundLayer;
446 if (extended && (obj->type != OTContainer))
448 fscanf(file, " (%i, %f, %i)\n", &obj->color, &obj->thickness, &obj->style);
456 /*static*/ bool FileIO::WriteObjectToFile(FILE * file, Object * obj)
465 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);
468 fprintf(file, "CIRCLE %i (%lf,%lf) %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]);
473 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]);
478 // fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, dimensionType);
479 fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y, ((Dimension *)obj)->subtype, ((Dimension *)obj)->offset);
484 fprintf(file, "TEXT %i (%lf,%lf) \"%s\"", obj->layer, obj->p[0].x, obj->p[0].y, ((Text *)obj)->s.c_str());
488 Container * c = (Container *)obj;
490 if (c->topLevel == false)
491 fprintf(file, "CONTAINER %i\n", obj->layer);
493 std::vector<void *>::iterator i;
495 for(i=c->objects.begin(); i!=c->objects.end(); i++)
496 WriteObjectToFile(file, (Object *)*i);
498 if (c->topLevel == false)
499 fprintf(file, "ENDCONTAINER\n");
507 if (obj->type != OTContainer)
508 fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style);