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 tau). 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
113 enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFObject, OTFEndOfFile };
115 // Instantiate class variables
116 /*static*/ int FileIO::objectFileType = OTFObject;
118 /*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * c)
120 /* Approach: loop through the container, doing a depth-first traversal. Any
121 extra containers found are looped through until there aren't any more
122 down, then ordinary objects are described. This can be handled by a
123 virtual Object function that reports the object by itself if it's a non-
124 Container, otherwise it enumerates all objects within itself. */
126 fprintf(file, "ARCHITEKTONAS DRAWING V1.2\n");
127 fprintf(file, "PROPERTIES 4\n");
128 fprintf(file, "BASE_UNIT %i\n", c->baseUnit);
129 fprintf(file, "UNIT_STYLE %i\n", c->unitStyle);
130 fprintf(file, "DEC_PREC %i\n", c->decimalPrecision);
131 fprintf(file, "FRAC_PREC %i\n", c->fractionalPrecision);
133 fprintf(file, "LAYERS %i\n", Global::numLayers);
135 for(int i=0; i<Global::numLayers; i++)
136 fprintf(file, "%i %i \"%s\"\n", (Global::layerHidden[i] ? 1 : 0), (Global::layerLocked[i] ? 1 : 0), Global::layerName[i].c_str());
138 fprintf(file, "ACTIVE %i\n", Global::activeLayer);
139 WriteObjectToFile(file, (Object *)c);
140 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);
165 /*static*/ void FileIO::ResetLayerVectors(void)
167 // Set up layer vectors
168 Global::layerHidden.insert(Global::layerHidden.begin(), false);
169 Global::layerLocked.insert(Global::layerLocked.begin(), false);
170 Global::layerName.insert(Global::layerName.begin(), "Background");
171 Global::numLayers = 1;
172 Global::activeLayer = 0;
175 /*static*/ bool FileIO::LoadVersion1_0(FILE * file, Container * drawing)
177 // Approach: read each object in the file, one by one. If the object is a
178 // Container, add objects to it until an "endContainer" marker is found.
179 // This will require a stack to maintain the current Container.
180 std::vector<Container *> containerStack;
181 Container * currentTopContainer = drawing;
186 // Reconstruct the object
187 Object * obj = GetObjectFromFile(file);
189 // objectFileType is set in GetObjectFromFile()...
190 if (objectFileType == OTFObject)
195 currentTopContainer->objects.push_back(obj);
196 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
198 // If the object is a container, push current TLC on the stack and
199 // set it as the new TLC
200 if (obj->type == OTContainer)
202 containerStack.push_back(currentTopContainer);
203 currentTopContainer = (Container *)obj;
206 else if (objectFileType == OTFContainerEnd)
208 // Container is done, so pop the stack to get back the previous TLC
209 currentTopContainer = containerStack.back();
210 containerStack.pop_back();
212 else if (objectFileType == OTFEndOfFile)
214 //printf("Load: container size = %li\n", drawing->objects.size());
222 /*static*/ bool FileIO::LoadVersion1_1(FILE * file, Container * drawing)
224 // Approach: read each object in the file, one by one. If the object is a
225 // Container, add objects to it until an "endContainer" marker is found.
226 // This will require a stack to maintain the current Container.
227 std::vector<Container *> containerStack;
228 Container * currentTopContainer = drawing;
233 // Reconstruct the object (extended format!)
234 Object * obj = GetObjectFromFile(file, true);
236 // objectFileType is set in GetObjectFromFile()...
237 if (objectFileType == OTFObject)
242 currentTopContainer->objects.push_back(obj);
243 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
245 // If the object is a container, push current TLC on the stack and
246 // set it as the new TLC
247 if (obj->type == OTContainer)
249 containerStack.push_back(currentTopContainer);
250 currentTopContainer = (Container *)obj;
253 else if (objectFileType == OTFContainerEnd)
255 // Add the extents of the current container
256 Rect r = ApplicationWindow::drawing->GetObjectExtents((Object *)currentTopContainer);
257 currentTopContainer->p[0] = r.TopLeft();
258 currentTopContainer->p[1] = r.BottomRight();
259 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\n", r.l, r.t, r.r, r.b);
261 // Container is done, so pop the stack to get back the previous TLC
262 currentTopContainer = containerStack.back();
263 containerStack.pop_back();
265 else if (objectFileType == OTFEndOfFile)
267 //printf("Load: container size = %li\n", drawing->objects.size());
275 /*static*/ bool FileIO::LoadVersion1_2(FILE * file, Container * drawing)
277 int hidden, locked, props = 0;
278 char textBuffer[65536];
280 // Load drawing properties, if any, first
281 fscanf(file, "PROPERTIES %i\n", &props);
285 fscanf(file, "BASE_UNIT %i\n", &(drawing->baseUnit));
286 fscanf(file, "UNIT_STYLE %i\n", &(drawing->unitStyle));
287 fscanf(file, "DEC_PREC %i\n", &(drawing->decimalPrecision));
288 fscanf(file, "FRAC_PREC %i\n", &(drawing->fractionalPrecision));
291 // Load layer information next
292 fscanf(file, "LAYERS %i\n", &Global::numLayers);
294 for(int i=0; i<Global::numLayers; i++)
296 fscanf(file, "%i %i \"%[^\"]\"\n", &hidden, &locked, textBuffer);
297 Global::layerHidden.push_back(hidden ? true : false);
298 Global::layerLocked.push_back(locked ? true : false);
299 Global::layerName.push_back(textBuffer);
302 fscanf(file, "ACTIVE %i\n", &Global::activeLayer);
304 // Approach: read each object in the file, one by one. If the object is a
305 // Container, add objects to it until an "endContainer" marker is found.
306 // This will require a stack to maintain the current Container.
307 std::vector<Container *> containerStack;
308 Container * currentTopContainer = drawing;
312 // Reconstruct the object (extended format!)
313 Object * obj = GetObjectFromFile(file, true, true);
315 // objectFileType is set in GetObjectFromFile()...
316 if (objectFileType == OTFObject)
321 currentTopContainer->objects.push_back(obj);
322 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
324 // If the object is a container, push current TLC on the stack and
325 // set it as the new TLC
326 if (obj->type == OTContainer)
328 containerStack.push_back(currentTopContainer);
329 currentTopContainer = (Container *)obj;
332 else if (objectFileType == OTFContainerEnd)
334 // Add the extents of the current container
335 Rect r = ApplicationWindow::drawing->GetObjectExtents((Object *)currentTopContainer);
336 currentTopContainer->p[0] = r.TopLeft();
337 currentTopContainer->p[1] = r.BottomRight();
338 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\n", r.l, r.t, r.r, r.b);
340 // Container is done, so pop the stack to get back the previous TLC
341 currentTopContainer = containerStack.back();
342 containerStack.pop_back();
344 else if (objectFileType == OTFEndOfFile)
346 //printf("Load: container size = %li\n", drawing->objects.size());
354 /*static*/ Object * FileIO::GetObjectFromFile(FILE * file, bool extended/*= false*/, bool ext2/*= false*/)
357 char textBuffer[65536];
359 /*int num =*/ fscanf(file, "%s", buffer);
361 objectFileType = OTFObject;
362 //printf("FileIO: fscanf returned %i, buffer = \"%s\"\n", num, buffer);
364 // The following fugliness is for troubleshooting. Can remove later.
365 if ((strcmp(buffer, "END") != 0) && (strcmp(buffer, "ENDCONTAINER") != 0))
368 /*num =*/ fscanf(file, " %i ", &foundLayer);
369 //printf("FileIO: fscanf returned %i, foundLayer = %i\n", num, foundLayer);
374 else if (errno == EBADF)
376 else if (errno == EILSEQ)
378 else if (errno == EINTR)
380 else if (errno == EINVAL)
382 else if (errno == ENOMEM)
384 else if (errno == ERANGE)
387 printf("errno = %i\n", errno);
391 // Need to add pen attributes as well... do that for v1.1 :-P
392 // And fill attributes... do that for v1.3 (1.2 added layers)
393 if (strcmp(buffer, "LINE") == 0)
396 fscanf(file, "(%lf,%lf) (%lf,%lf)", &p1.x, &p1.y, &p2.x, &p2.y);
397 obj = (Object *)new Line(p1, p2);
399 else if (strcmp(buffer, "CIRCLE") == 0)
403 fscanf(file, "(%lf,%lf) %lf", &p.x, &p.y, &r);
404 obj = (Object *)new Circle(p, r);
406 else if (strcmp(buffer, "ARC") == 0)
410 fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &p.x, &p.y, &r, &a1, &a2);
411 obj = (Object *)new Arc(p, r, a1, a2);
413 else if (strcmp(buffer, "POLYLINE") == 0)
416 obj = (Object *)new Polyline();
417 fscanf(file, "(%li)", &size);
418 fscanf(file, " (%i, %f, %i)\n", &obj->color, &obj->thickness, &obj->style);
420 for(int i=0; i<size; i++)
422 Object * po = new Object();
423 fscanf(file, "(%lf,%lf,%lf)\n", &po->p[0].x, &po->p[0].y, &po->length);
424 ((Polyline *)obj)->Add(po);
427 else if (strcmp(buffer, "TEXT") == 0)
430 fscanf(file, "(%lf,%lf) \"%[^\"]\"", &p.x, &p.y, textBuffer);
431 obj = (Object *)new Text(p, textBuffer);
433 else if (strcmp(buffer, "DIMENSION") == 0)
438 fscanf(file, "(%lf,%lf) (%lf,%lf) %i", &p1.x, &p1.y, &p2.x, &p2.y, (int *)&type);
441 fscanf(file, " %lf", &offset);
443 obj = (Object *)new Dimension(p1, p2, type, offset);
445 else if (strcmp(buffer, "CONTAINER") == 0)
447 obj = (Object *)new Container();
448 // objectFileType = OTFContainer;
450 else if (strcmp(buffer, "ENDCONTAINER") == 0)
452 objectFileType = OTFContainerEnd;
454 else if (strcmp(buffer, "END") == 0)
456 objectFileType = OTFEndOfFile;
459 printf("Unknown object type '%s'...\n", buffer);
463 obj->layer = foundLayer;
465 if (extended && (obj->type != OTContainer) && (obj->type != OTPolyline))
467 fscanf(file, " (%i, %f, %i)\n", &obj->color, &obj->thickness, &obj->style);
474 /*static*/ bool FileIO::WriteObjectToFile(FILE * file, Object * obj)
483 fprintf(file, "LINE %i (%.16lf,%.16lf) (%.16lf,%.16lf)", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y);
486 fprintf(file, "CIRCLE %i (%.16lf,%.16lf) %.16lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]);
491 fprintf(file, "ARC %i (%.16lf,%.16lf) %.16lf, %.16lf, %.16lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0], obj->angle[0], obj->angle[1]);
495 Polyline * p = (Polyline *)obj;
496 fprintf(file, "POLYLINE %i (%li)", p->layer, p->points.size());
497 fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style);
499 for(VPVectorIter i=p->points.begin(); i!=p->points.end(); i++)
501 Object * po = (Object *)(*i);
502 fprintf(file, "(%.16lf,%.16lf,%.16lf)\n", po->p[0].x, po->p[0].y, po->length);
507 fprintf(file, "DIMENSION %i (%.16lf,%.16lf) (%.16lf,%.16lf) %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);
512 fprintf(file, "TEXT %i (%lf,%lf) \"%s\"", obj->layer, obj->p[0].x, obj->p[0].y, ((Text *)obj)->s.c_str());
516 Container * c = (Container *)obj;
518 if (c->topLevel == false)
519 fprintf(file, "CONTAINER %i\n", obj->layer);
521 std::vector<void *>::iterator i;
523 for(i=c->objects.begin(); i!=c->objects.end(); i++)
524 WriteObjectToFile(file, (Object *)*i);
526 if (c->topLevel == false)
527 fprintf(file, "ENDCONTAINER\n");
535 if ((obj->type != OTContainer) && (obj->type != OTPolyline))
536 fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style);