]> Shamusworld >> Repos - architektonas/blob - src/fileio.cpp
1b11902e37b672f1f6a72bd6dc443894acce5efd
[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
114 //enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFLine, OTFCircle, OTFArc,
115 //      OTFDimension, OTFPolygon, OTFText, OTFImage, OTFBlock, OTFEndOfFile };
116 enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFObject, OTFEndOfFile };
117
118 // Instantiate class variables
119 /*static*/ int FileIO::objectFileType = OTFObject;
120
121 /*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * c)
122 {
123         /* Approach: loop through the container, doing a depth-first traversal. Any
124            extra containers found are looped through until there aren't any more
125            down, then ordinary objects are described. This can be handled by a
126            virtual Object function that reports the object by itself if it's a non-
127            Container, otherwise it enumerates all objects within itself. */
128
129         fprintf(file, "ARCHITEKTONAS DRAWING V1.2\n");
130         fprintf(file, "LAYERS %i\n", Global::numLayers);
131
132         for(int i=0; i<Global::numLayers; i++)
133                 fprintf(file, "%i %i \"%s\"\n", (Global::layerHidden[i] ? 1 : 0), (Global::layerLocked[i] ? 1 : 0), Global::layerName[i].c_str());
134
135         fprintf(file, "ACTIVE %i\n", Global::activeLayer);
136         WriteObjectToFile(file, (Object *)c);
137         fprintf(file, "END\n");
138         return true;
139 }
140
141 /*static*/ bool FileIO::LoadAtnsFile(FILE * file, Container * drawing)
142 {
143         float version;
144
145         fscanf(file, "ARCHITEKTONAS DRAWING V%f\n", &version);
146
147         // Clear out layer vectors
148         Global::layerHidden.clear();
149         Global::layerLocked.clear();
150         Global::layerName.clear();
151
152         if (version == 1.0f)
153                 return LoadVersion1_0(file, drawing);
154         else if (version == 1.1f)
155                 return LoadVersion1_1(file, drawing);
156         else if (version == 1.2f)
157                 return LoadVersion1_2(file, drawing);
158
159 //printf("LoadAtnsFile: Could not locate version! (version=%f)\n", version);
160         return false;
161 }
162
163 /*static*/ void FileIO::ResetLayerVectors(void)
164 {
165         // Set up layer vectors
166         Global::layerHidden.insert(Global::layerHidden.begin(), false);
167         Global::layerLocked.insert(Global::layerLocked.begin(), false);
168         Global::layerName.insert(Global::layerName.begin(), "Background");
169         Global::numLayers = 1;
170         Global::activeLayer = 0;
171 }
172
173 /*static*/ bool FileIO::LoadVersion1_0(FILE * file, Container * drawing)
174 {
175         // Approach: read each object in the file, one by one. If the object is a
176         // Container, add objects to it until an "endContainer" marker is found.
177         // This will require a stack to maintain the current Container.
178         std::vector<Container *> containerStack;
179         Container * currentTopContainer = drawing;
180         ResetLayerVectors();
181
182         while (!feof(file))
183         {
184                 // Reconstruct the object
185                 Object * obj = GetObjectFromFile(file);
186
187                 // objectFileType is set in GetObjectFromFile()...
188                 if (objectFileType == OTFObject)
189                 {
190                         if (obj == NULL)
191                                 return false;
192
193                         currentTopContainer->objects.push_back(obj);
194 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
195
196                         // If the object is a container, push current TLC on the stack and
197                         // set it as the new TLC
198                         if (obj->type == OTContainer)
199                         {
200                                 containerStack.push_back(currentTopContainer);
201                                 currentTopContainer = (Container *)obj;
202                         }
203                 }
204                 else if (objectFileType == OTFContainerEnd)
205                 {
206                         // Container is done, so pop the stack to get back the previous TLC
207                         currentTopContainer = containerStack.back();
208                         containerStack.pop_back();
209                 }
210                 else if (objectFileType == OTFEndOfFile)
211                 {
212 //printf("Load: container size = %li\n", drawing->objects.size());
213                         return true;
214                 }
215         }
216
217         return false;
218 }
219
220 /*static*/ bool FileIO::LoadVersion1_1(FILE * file, Container * drawing)
221 {
222         // Approach: read each object in the file, one by one. If the object is a
223         // Container, add objects to it until an "endContainer" marker is found.
224         // This will require a stack to maintain the current Container.
225         std::vector<Container *> containerStack;
226         Container * currentTopContainer = drawing;
227         ResetLayerVectors();
228
229         while (!feof(file))
230         {
231                 // Reconstruct the object (extended format!)
232                 Object * obj = GetObjectFromFile(file, true);
233
234                 // objectFileType is set in GetObjectFromFile()...
235                 if (objectFileType == OTFObject)
236                 {
237                         if (obj == NULL)
238                                 return false;
239
240                         currentTopContainer->objects.push_back(obj);
241 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
242
243                         // If the object is a container, push current TLC on the stack and
244                         // set it as the new TLC
245                         if (obj->type == OTContainer)
246                         {
247                                 containerStack.push_back(currentTopContainer);
248                                 currentTopContainer = (Container *)obj;
249                         }
250                 }
251                 else if (objectFileType == OTFContainerEnd)
252                 {
253                         // Add the extents of the current container
254                         Rect r = ApplicationWindow::drawing->GetObjectExtents((Object *)currentTopContainer);
255                         currentTopContainer->p[0] = r.TopLeft();
256                         currentTopContainer->p[1] = r.BottomRight();
257 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\n", r.l, r.t, r.r, r.b);
258
259                         // Container is done, so pop the stack to get back the previous TLC
260                         currentTopContainer = containerStack.back();
261                         containerStack.pop_back();
262                 }
263                 else if (objectFileType == OTFEndOfFile)
264                 {
265 //printf("Load: container size = %li\n", drawing->objects.size());
266                         return true;
267                 }
268         }
269
270         return false;
271 }
272
273 /*static*/ bool FileIO::LoadVersion1_2(FILE * file, Container * drawing)
274 {
275         int hidden, locked;
276         char textBuffer[65536];
277
278         // Load layer information first
279         fscanf(file, "LAYERS %i\n", &Global::numLayers);
280
281         for(int i=0; i<Global::numLayers; i++)
282         {
283                 fscanf(file, "%i %i \"%[^\"]\"\n", &hidden, &locked, textBuffer);
284                 Global::layerHidden.push_back(hidden ? true : false);
285                 Global::layerLocked.push_back(locked ? true : false);
286                 Global::layerName.push_back(textBuffer);
287         }
288
289         fscanf(file, "ACTIVE %i\n", &Global::activeLayer);
290
291         // Approach: read each object in the file, one by one. If the object is a
292         // Container, add objects to it until an "endContainer" marker is found.
293         // This will require a stack to maintain the current Container.
294         std::vector<Container *> containerStack;
295         Container * currentTopContainer = drawing;
296
297         while (!feof(file))
298         {
299                 // Reconstruct the object (extended format!)
300                 Object * obj = GetObjectFromFile(file, true, true);
301
302                 // objectFileType is set in GetObjectFromFile()...
303                 if (objectFileType == OTFObject)
304                 {
305                         if (obj == NULL)
306                                 return false;
307
308                         currentTopContainer->objects.push_back(obj);
309 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
310
311                         // If the object is a container, push current TLC on the stack and
312                         // set it as the new TLC
313                         if (obj->type == OTContainer)
314                         {
315                                 containerStack.push_back(currentTopContainer);
316                                 currentTopContainer = (Container *)obj;
317                         }
318                 }
319                 else if (objectFileType == OTFContainerEnd)
320                 {
321                         // Add the extents of the current container
322                         Rect r = ApplicationWindow::drawing->GetObjectExtents((Object *)currentTopContainer);
323                         currentTopContainer->p[0] = r.TopLeft();
324                         currentTopContainer->p[1] = r.BottomRight();
325 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\n", r.l, r.t, r.r, r.b);
326
327                         // Container is done, so pop the stack to get back the previous TLC
328                         currentTopContainer = containerStack.back();
329                         containerStack.pop_back();
330                 }
331                 else if (objectFileType == OTFEndOfFile)
332                 {
333 //printf("Load: container size = %li\n", drawing->objects.size());
334                         return true;
335                 }
336         }
337
338         return false;
339 }
340
341 /*static*/ Object * FileIO::GetObjectFromFile(FILE * file, bool extended/*= false*/, bool ext2/*= false*/)
342 {
343         char buffer[256];
344         char textBuffer[65536];
345         int foundLayer = 0;
346         /*int num =*/ fscanf(file, "%s", buffer);
347         Object * obj = NULL;
348         objectFileType = OTFObject;
349 //printf("FileIO: fscanf returned %i, buffer = \"%s\"\n", num, buffer);
350
351 // The following fugliness is for troubleshooting. Can remove later.
352         if ((strcmp(buffer, "END") != 0) && (strcmp(buffer, "ENDCONTAINER") != 0))
353 {
354 errno = 0;
355                 /*num =*/ fscanf(file, " %i ", &foundLayer);
356 //printf("FileIO: fscanf returned %i, foundLayer = %i\n", num, foundLayer);
357 if (errno)
358 {
359         if (errno == EAGAIN)
360                 printf("EAGAIN\n");
361         else if (errno == EBADF)
362                 printf("EBADF\n");
363         else if (errno == EILSEQ)
364                 printf("EILSEQ\n");
365         else if (errno == EINTR)
366                 printf("EINTR\n");
367         else if (errno == EINVAL)
368                 printf("EINVAL\n");
369         else if (errno == ENOMEM)
370                 printf("ENOMEM\n");
371         else if (errno == ERANGE)
372                 printf("ERANGE\n");
373         else
374                 printf("errno = %i\n", errno);
375 }
376 }
377
378         // Need to add pen attributes as well... do that for v1.1 :-P
379         // And fill attributes... do that for v1.3 (1.2 added layers)
380         if (strcmp(buffer, "LINE") == 0)
381         {
382                 Point p1, p2;
383                 fscanf(file, "(%lf,%lf) (%lf,%lf)", &p1.x, &p1.y, &p2.x, &p2.y);
384                 obj = (Object *)new Line(p1, p2);
385         }
386         else if (strcmp(buffer, "CIRCLE") == 0)
387         {
388                 Point p;
389                 double r;
390                 fscanf(file, "(%lf,%lf) %lf", &p.x, &p.y, &r);
391                 obj = (Object *)new Circle(p, r);
392         }
393         else if (strcmp(buffer, "ARC") == 0)
394         {
395                 Point p;
396                 double r, a1, a2;
397                 fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &p.x, &p.y, &r, &a1, &a2);
398                 obj = (Object *)new Arc(p, r, a1, a2);
399         }
400         else if (strcmp(buffer, "TEXT") == 0)
401         {
402                 Point p;
403                 fscanf(file, "(%lf,%lf) \"%[^\"]\"", &p.x, &p.y, textBuffer);
404                 obj = (Object *)new Text(p, textBuffer);
405         }
406         else if (strcmp(buffer, "DIMENSION") == 0)
407         {
408                 Point p1, p2;
409                 DimensionType type;
410                 double offset = 0;
411                 fscanf(file, "(%lf,%lf) (%lf,%lf) %i", &p1.x, &p1.y, &p2.x, &p2.y, (int *)&type);
412
413                 if (ext2)
414                         fscanf(file, " %lf", &offset);
415
416                 obj = (Object *)new Dimension(p1, p2, type, offset);
417         }
418         else if (strcmp(buffer, "CONTAINER") == 0)
419         {
420                 obj = (Object *)new Container();
421 //              objectFileType = OTFContainer;
422         }
423         else if (strcmp(buffer, "ENDCONTAINER") == 0)
424         {
425                 objectFileType = OTFContainerEnd;
426         }
427         else if (strcmp(buffer, "END") == 0)
428         {
429                 objectFileType = OTFEndOfFile;
430         }
431         else
432                 printf("Unknown object type '%s'...\n", buffer);
433
434         if (obj != NULL)
435         {
436                 obj->layer = foundLayer;
437
438                 if (extended && (obj->type != OTContainer))
439                 {
440                         fscanf(file, " (%i, %f, %i)\n", &obj->color, &obj->thickness, &obj->style);
441                 }
442         }
443
444         return obj;
445 }
446
447 /*static*/ bool FileIO::WriteObjectToFile(FILE * file, Object * obj)
448 {
449         // Sanity check
450         if (obj == NULL)
451                 return false;
452
453         switch (obj->type)
454         {
455         case OTLine:
456                 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);
457                 break;
458         case OTCircle:
459                 fprintf(file, "CIRCLE %i (%lf,%lf) %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]);
460                 break;
461         case OTEllipse:
462                 break;
463         case OTArc:
464                 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]);
465                 break;
466         case OTPolygon:
467                 break;
468         case OTDimension:
469 //              fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, dimensionType);
470                 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);
471                 break;
472         case OTSpline:
473                 break;
474         case OTText:
475                 fprintf(file, "TEXT %i (%lf,%lf) \"%s\"", obj->layer, obj->p[0].x, obj->p[0].y, ((Text *)obj)->s.c_str());
476                 break;
477         case OTContainer:
478         {
479                 Container * c = (Container *)obj;
480
481                 if (c->topLevel == false)
482                         fprintf(file, "CONTAINER %i\n", obj->layer);
483
484                 std::vector<void *>::iterator i;
485
486                 for(i=c->objects.begin(); i!=c->objects.end(); i++)
487                         WriteObjectToFile(file, (Object *)*i);
488
489                 if (c->topLevel == false)
490                         fprintf(file, "ENDCONTAINER\n");
491
492                 break;
493         }
494         default:
495                 break;
496         }
497
498         if (obj->type != OTContainer)
499                 fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style);
500
501         return true;
502 }