]> Shamusworld >> Repos - architektonas/blob - src/fileio.cpp
Further progress on Polylines: Polylines can be selected and moved.
[architektonas] / src / fileio.cpp
1 //
2 // fileio.cpp: Architektonas file save/load support
3 //
4 // Part of the Architektonas Project
5 // (C) 2013 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // Who  When        What
11 // ---  ----------  ------------------------------------------------------------
12 // JLH  02/20/2013  Created this file
13 //
14
15 #include "fileio.h"
16
17 //#include <stdio.h>
18 #include <errno.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <vector>
22 #include "applicationwindow.h"
23 #include "drawingview.h"
24 #include "global.h"
25 #include "structs.h"
26
27 /*
28 How to handle connected objects
29 -------------------------------
30
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.
34
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):
38
39 CONNECTIONS
40 1: 12 3
41 3: 1
42 12: 1
43 ENDCONNECTIONS
44
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.
49
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.
53
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
56 endpoints.
57
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?
61
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.
66
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:
69
70 Object *: dim1, t = 0
71 Object *: dim1, t = 1
72
73 Dimension looks like this:
74
75 Object *: line1, t = 0
76 Object *: line1, t = 1
77
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.
81
82
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.
87
88 How to describe them and their connections (or lack thereof)?
89
90 Would have to be a 2nd pass, after all objects have been written out in order.
91 Then you could do something like:
92
93 DIMCONNECTIONS
94 8 (the Dimension #): 1 (the Object # for point 1) 1 (the Object # for point 2)
95 ENDDIMCONNECTIONS
96
97
98
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.
103
104
105 OTHER CONSIDERATIONS:
106 ---------------------
107
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
110
111 */
112
113 enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFObject, OTFEndOfFile };
114
115 // Instantiate class variables
116 /*static*/ int FileIO::objectFileType = OTFObject;
117
118 /*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * c)
119 {
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. */
125
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);
132
133         fprintf(file, "LAYERS %i\n", Global::numLayers);
134
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());
137
138         fprintf(file, "ACTIVE %i\n", Global::activeLayer);
139         WriteObjectToFile(file, (Object *)c);
140         fprintf(file, "END\n");
141         return true;
142 }
143
144 /*static*/ bool FileIO::LoadAtnsFile(FILE * file, Container * drawing)
145 {
146         float version;
147
148         fscanf(file, "ARCHITEKTONAS DRAWING V%f\n", &version);
149
150         // Clear out layer vectors
151         Global::layerHidden.clear();
152         Global::layerLocked.clear();
153         Global::layerName.clear();
154
155         if (version == 1.0f)
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);
161
162         return false;
163 }
164
165 /*static*/ void FileIO::ResetLayerVectors(void)
166 {
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;
173 }
174
175 /*static*/ bool FileIO::LoadVersion1_0(FILE * file, Container * drawing)
176 {
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;
182         ResetLayerVectors();
183
184         while (!feof(file))
185         {
186                 // Reconstruct the object
187                 Object * obj = GetObjectFromFile(file);
188
189                 // objectFileType is set in GetObjectFromFile()...
190                 if (objectFileType == OTFObject)
191                 {
192                         if (obj == NULL)
193                                 return false;
194
195                         currentTopContainer->objects.push_back(obj);
196 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
197
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)
201                         {
202                                 containerStack.push_back(currentTopContainer);
203                                 currentTopContainer = (Container *)obj;
204                         }
205                 }
206                 else if (objectFileType == OTFContainerEnd)
207                 {
208                         // Container is done, so pop the stack to get back the previous TLC
209                         currentTopContainer = containerStack.back();
210                         containerStack.pop_back();
211                 }
212                 else if (objectFileType == OTFEndOfFile)
213                 {
214 //printf("Load: container size = %li\n", drawing->objects.size());
215                         return true;
216                 }
217         }
218
219         return false;
220 }
221
222 /*static*/ bool FileIO::LoadVersion1_1(FILE * file, Container * drawing)
223 {
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;
229         ResetLayerVectors();
230
231         while (!feof(file))
232         {
233                 // Reconstruct the object (extended format!)
234                 Object * obj = GetObjectFromFile(file, true);
235
236                 // objectFileType is set in GetObjectFromFile()...
237                 if (objectFileType == OTFObject)
238                 {
239                         if (obj == NULL)
240                                 return false;
241
242                         currentTopContainer->objects.push_back(obj);
243 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
244
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)
248                         {
249                                 containerStack.push_back(currentTopContainer);
250                                 currentTopContainer = (Container *)obj;
251                         }
252                 }
253                 else if (objectFileType == OTFContainerEnd)
254                 {
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);
260
261                         // Container is done, so pop the stack to get back the previous TLC
262                         currentTopContainer = containerStack.back();
263                         containerStack.pop_back();
264                 }
265                 else if (objectFileType == OTFEndOfFile)
266                 {
267 //printf("Load: container size = %li\n", drawing->objects.size());
268                         return true;
269                 }
270         }
271
272         return false;
273 }
274
275 /*static*/ bool FileIO::LoadVersion1_2(FILE * file, Container * drawing)
276 {
277         int hidden, locked, props = 0;
278         char textBuffer[65536];
279
280         // Load drawing properties, if any, first
281         fscanf(file, "PROPERTIES %i\n", &props);
282
283         if (props == 4)
284         {
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));
289         }
290
291         // Load layer information next
292         fscanf(file, "LAYERS %i\n", &Global::numLayers);
293
294         for(int i=0; i<Global::numLayers; i++)
295         {
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);
300         }
301
302         fscanf(file, "ACTIVE %i\n", &Global::activeLayer);
303
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;
309
310         while (!feof(file))
311         {
312                 // Reconstruct the object (extended format!)
313                 Object * obj = GetObjectFromFile(file, true, true);
314
315                 // objectFileType is set in GetObjectFromFile()...
316                 if (objectFileType == OTFObject)
317                 {
318                         if (obj == NULL)
319                                 return false;
320
321                         currentTopContainer->objects.push_back(obj);
322 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
323
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)
327                         {
328                                 containerStack.push_back(currentTopContainer);
329                                 currentTopContainer = (Container *)obj;
330                         }
331                 }
332                 else if (objectFileType == OTFContainerEnd)
333                 {
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);
339
340                         // Container is done, so pop the stack to get back the previous TLC
341                         currentTopContainer = containerStack.back();
342                         containerStack.pop_back();
343                 }
344                 else if (objectFileType == OTFEndOfFile)
345                 {
346 //printf("Load: container size = %li\n", drawing->objects.size());
347                         return true;
348                 }
349         }
350
351         return false;
352 }
353
354 /*static*/ Object * FileIO::GetObjectFromFile(FILE * file, bool extended/*= false*/, bool ext2/*= false*/)
355 {
356         char buffer[256];
357         char textBuffer[65536];
358         int foundLayer = 0;
359         /*int num =*/ fscanf(file, "%s", buffer);
360         Object * obj = NULL;
361         objectFileType = OTFObject;
362 //printf("FileIO: fscanf returned %i, buffer = \"%s\"\n", num, buffer);
363
364 // The following fugliness is for troubleshooting. Can remove later.
365         if ((strcmp(buffer, "END") != 0) && (strcmp(buffer, "ENDCONTAINER") != 0))
366 {
367 errno = 0;
368                 /*num =*/ fscanf(file, " %i ", &foundLayer);
369 //printf("FileIO: fscanf returned %i, foundLayer = %i\n", num, foundLayer);
370 if (errno)
371 {
372         if (errno == EAGAIN)
373                 printf("EAGAIN\n");
374         else if (errno == EBADF)
375                 printf("EBADF\n");
376         else if (errno == EILSEQ)
377                 printf("EILSEQ\n");
378         else if (errno == EINTR)
379                 printf("EINTR\n");
380         else if (errno == EINVAL)
381                 printf("EINVAL\n");
382         else if (errno == ENOMEM)
383                 printf("ENOMEM\n");
384         else if (errno == ERANGE)
385                 printf("ERANGE\n");
386         else
387                 printf("errno = %i\n", errno);
388 }
389 }
390
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)
394         {
395                 Point p1, p2;
396                 fscanf(file, "(%lf,%lf) (%lf,%lf)", &p1.x, &p1.y, &p2.x, &p2.y);
397                 obj = (Object *)new Line(p1, p2);
398         }
399         else if (strcmp(buffer, "CIRCLE") == 0)
400         {
401                 Point p;
402                 double r;
403                 fscanf(file, "(%lf,%lf) %lf", &p.x, &p.y, &r);
404                 obj = (Object *)new Circle(p, r);
405         }
406         else if (strcmp(buffer, "ARC") == 0)
407         {
408                 Point p;
409                 double r, a1, a2;
410                 fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &p.x, &p.y, &r, &a1, &a2);
411                 obj = (Object *)new Arc(p, r, a1, a2);
412         }
413         else if (strcmp(buffer, "POLYLINE") == 0)
414         {
415                 long int size;
416                 std::vector<Point> pts;
417                 uint32_t color;
418                 float thickness;
419                 int style;
420
421                 fscanf(file, "(%li)", &size);
422                 fscanf(file, " (%i, %f, %i)\n", &color, &thickness, &style);
423
424                 for(int i=0; i<size; i++)
425                 {
426                         Point p;
427                         fscanf(file, "(%lf,%lf,%lf)\n", &p.x, &p.y, &p.b);
428                         pts.push_back(p);
429                 }
430
431                 obj = (Object *)new Polyline(pts, thickness, color, style);
432         }
433         else if (strcmp(buffer, "TEXT") == 0)
434         {
435                 Point p;
436                 fscanf(file, "(%lf,%lf) \"%[^\"]\"", &p.x, &p.y, textBuffer);
437                 obj = (Object *)new Text(p, textBuffer);
438         }
439         else if (strcmp(buffer, "DIMENSION") == 0)
440         {
441                 Point p1, p2;
442                 DimensionType type;
443                 double offset = 0;
444                 fscanf(file, "(%lf,%lf) (%lf,%lf) %i", &p1.x, &p1.y, &p2.x, &p2.y, (int *)&type);
445
446                 if (ext2)
447                         fscanf(file, " %lf", &offset);
448
449                 obj = (Object *)new Dimension(p1, p2, type, offset);
450         }
451         else if (strcmp(buffer, "CONTAINER") == 0)
452         {
453                 obj = (Object *)new Container();
454 //              objectFileType = OTFContainer;
455         }
456         else if (strcmp(buffer, "ENDCONTAINER") == 0)
457         {
458                 objectFileType = OTFContainerEnd;
459         }
460         else if (strcmp(buffer, "END") == 0)
461         {
462                 objectFileType = OTFEndOfFile;
463         }
464         else
465                 printf("Unknown object type '%s'...\n", buffer);
466
467         if (obj != NULL)
468         {
469                 obj->layer = foundLayer;
470
471                 if (extended && (obj->type != OTContainer) && (obj->type != OTPolyline))
472                 {
473                         fscanf(file, " (%i, %f, %i)\n", &obj->color, &obj->thickness, &obj->style);
474                 }
475         }
476
477         return obj;
478 }
479
480 /*static*/ bool FileIO::WriteObjectToFile(FILE * file, Object * obj)
481 {
482         // Sanity check
483         if (obj == NULL)
484                 return false;
485
486         switch (obj->type)
487         {
488         case OTLine:
489                 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);
490                 break;
491         case OTCircle:
492                 fprintf(file, "CIRCLE %i (%.16lf,%.16lf) %.16lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]);
493                 break;
494         case OTEllipse:
495                 break;
496         case OTArc:
497                 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]);
498                 break;
499         case OTPolyline:
500         {
501                 Polyline * p = (Polyline *)obj;
502                 fprintf(file, "POLYLINE %i (%li)", p->layer, p->points.size());
503                 fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style);
504
505                 for(long unsigned int i=0; i<p->points.size(); i++)
506                 {
507                         fprintf(file, "(%.16lf,%.16lf,%.16lf)\n", p->points[i].x, p->points[i].y, p->points[i].b);
508                 }
509         }
510                 break;
511         case OTDimension:
512                 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);
513                 break;
514         case OTSpline:
515                 break;
516         case OTText:
517                 fprintf(file, "TEXT %i (%lf,%lf) \"%s\"", obj->layer, obj->p[0].x, obj->p[0].y, ((Text *)obj)->s.c_str());
518                 break;
519         case OTContainer:
520         {
521                 Container * c = (Container *)obj;
522
523                 if (c->topLevel == false)
524                         fprintf(file, "CONTAINER %i\n", obj->layer);
525
526                 std::vector<void *>::iterator i;
527
528                 for(i=c->objects.begin(); i!=c->objects.end(); i++)
529                         WriteObjectToFile(file, (Object *)*i);
530
531                 if (c->topLevel == false)
532                         fprintf(file, "ENDCONTAINER\n");
533
534                 break;
535         }
536         default:
537                 break;
538         }
539
540         if ((obj->type != OTContainer) && (obj->type != OTPolyline))
541                 fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style);
542
543         return true;
544 }