]> Shamusworld >> Repos - architektonas/blob - src/fileio.cpp
ace5bfa247713a3adc4aabee61f0dc3871a4c0aa
[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 2π). 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
119 // Instantiate class variables
120 /*static*/ int FileIO::objectFileType = OTFObject;
121
122
123 /*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * c)
124 {
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. */
130
131         fprintf(file, "ARCHITEKTONAS DRAWING V1.2\n");
132         fprintf(file, "LAYERS %i\n", Global::numLayers);
133
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());
136
137         fprintf(file, "ACTIVE %i\n", Global::activeLayer);
138         WriteObjectToFile(file, (Object *)c);
139         fprintf(file, "END\n");
140         return true;
141 }
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 //printf("LoadAtnsFile: Could not locate version! (version=%f)\n", version);
163         return false;
164 }
165
166
167 /*static*/ void FileIO::ResetLayerVectors(void)
168 {
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;
175 }
176
177
178 /*static*/ bool FileIO::LoadVersion1_0(FILE * file, Container * drawing)
179 {
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;
185         ResetLayerVectors();
186
187         while (!feof(file))
188         {
189                 // Reconstruct the object
190                 Object * obj = GetObjectFromFile(file);
191
192                 // objectFileType is set in GetObjectFromFile()...
193                 if (objectFileType == OTFObject)
194                 {
195                         if (obj == NULL)
196                                 return false;
197
198                         currentTopContainer->objects.push_back(obj);
199 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
200
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)
204                         {
205                                 containerStack.push_back(currentTopContainer);
206                                 currentTopContainer = (Container *)obj;
207                         }
208                 }
209                 else if (objectFileType == OTFContainerEnd)
210                 {
211                         // Container is done, so pop the stack to get back the previous TLC
212                         currentTopContainer = containerStack.back();
213                         containerStack.pop_back();
214                 }
215                 else if (objectFileType == OTFEndOfFile)
216                 {
217 //printf("Load: container size = %li\n", drawing->objects.size());
218                         return true;
219                 }
220         }
221
222         return false;
223 }
224
225
226 /*static*/ bool FileIO::LoadVersion1_1(FILE * file, Container * drawing)
227 {
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;
233         ResetLayerVectors();
234
235         while (!feof(file))
236         {
237                 // Reconstruct the object (extended format!)
238                 Object * obj = GetObjectFromFile(file, true);
239
240                 // objectFileType is set in GetObjectFromFile()...
241                 if (objectFileType == OTFObject)
242                 {
243                         if (obj == NULL)
244                                 return false;
245
246                         currentTopContainer->objects.push_back(obj);
247 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
248
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)
252                         {
253                                 containerStack.push_back(currentTopContainer);
254                                 currentTopContainer = (Container *)obj;
255                         }
256                 }
257                 else if (objectFileType == OTFContainerEnd)
258                 {
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);
264
265                         // Container is done, so pop the stack to get back the previous TLC
266                         currentTopContainer = containerStack.back();
267                         containerStack.pop_back();
268                 }
269                 else if (objectFileType == OTFEndOfFile)
270                 {
271 //printf("Load: container size = %li\n", drawing->objects.size());
272                         return true;
273                 }
274         }
275
276         return false;
277 }
278
279
280 /*static*/ bool FileIO::LoadVersion1_2(FILE * file, Container * drawing)
281 {
282         int hidden, locked;
283         char textBuffer[65536];
284
285         // Load layer information first
286         fscanf(file, "LAYERS %i\n", &Global::numLayers);
287
288         for(int i=0; i<Global::numLayers; i++)
289         {
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);
294         }
295
296         fscanf(file, "ACTIVE %i\n", &Global::activeLayer);
297
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;
303
304         while (!feof(file))
305         {
306                 // Reconstruct the object (extended format!)
307                 Object * obj = GetObjectFromFile(file, true);
308
309                 // objectFileType is set in GetObjectFromFile()...
310                 if (objectFileType == OTFObject)
311                 {
312                         if (obj == NULL)
313                                 return false;
314
315                         currentTopContainer->objects.push_back(obj);
316 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
317
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)
321                         {
322                                 containerStack.push_back(currentTopContainer);
323                                 currentTopContainer = (Container *)obj;
324                         }
325                 }
326                 else if (objectFileType == OTFContainerEnd)
327                 {
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);
333
334                         // Container is done, so pop the stack to get back the previous TLC
335                         currentTopContainer = containerStack.back();
336                         containerStack.pop_back();
337                 }
338                 else if (objectFileType == OTFEndOfFile)
339                 {
340 //printf("Load: container size = %li\n", drawing->objects.size());
341                         return true;
342                 }
343         }
344
345         return false;
346 }
347
348
349 /*static*/ Object * FileIO::GetObjectFromFile(FILE * file, bool extended/*= false*/)
350 {
351         char buffer[256];
352         char textBuffer[65536];
353         int foundLayer = 0;
354         /*int num =*/ fscanf(file, "%s", buffer);
355         Object * obj = NULL;
356         objectFileType = OTFObject;
357 //printf("FileIO: fscanf returned %i, buffer = \"%s\"\n", num, buffer);
358
359 // The following fugliness is for troubleshooting. Can remove later.
360         if ((strcmp(buffer, "END") != 0) && (strcmp(buffer, "ENDCONTAINER") != 0))
361 {
362 errno = 0;
363                 /*num =*/ fscanf(file, " %i ", &foundLayer);
364 //printf("FileIO: fscanf returned %i, foundLayer = %i\n", num, foundLayer);
365 if (errno)
366 {
367         if (errno == EAGAIN)
368                 printf("EAGAIN\n");
369         else if (errno == EBADF)
370                 printf("EBADF\n");
371         else if (errno == EILSEQ)
372                 printf("EILSEQ\n");
373         else if (errno == EINTR)
374                 printf("EINTR\n");
375         else if (errno == EINVAL)
376                 printf("EINVAL\n");
377         else if (errno == ENOMEM)
378                 printf("ENOMEM\n");
379         else if (errno == ERANGE)
380                 printf("ERANGE\n");
381         else
382                 printf("errno = %i\n", errno);
383 }
384 }
385
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)
389         {
390                 Point p1, p2;
391                 fscanf(file, "(%lf,%lf) (%lf,%lf)", &p1.x, &p1.y, &p2.x, &p2.y);
392                 obj = (Object *)new Line(p1, p2);
393         }
394         else if (strcmp(buffer, "CIRCLE") == 0)
395         {
396                 Point p;
397                 double r;
398                 fscanf(file, "(%lf,%lf) %lf", &p.x, &p.y, &r);
399                 obj = (Object *)new Circle(p, r);
400         }
401         else if (strcmp(buffer, "ARC") == 0)
402         {
403                 Point p;
404                 double r, a1, a2;
405                 fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &p.x, &p.y, &r, &a1, &a2);
406                 obj = (Object *)new Arc(p, r, a1, a2);
407         }
408         else if (strcmp(buffer, "TEXT") == 0)
409         {
410                 Point p;
411                 fscanf(file, "(%lf,%lf) \"%[^\"]\"", &p.x, &p.y, textBuffer);
412                 obj = (Object *)new Text(p, textBuffer);
413         }
414         else if (strcmp(buffer, "DIMENSION") == 0)
415         {
416                 Point p1, p2;
417                 DimensionType type;
418                 fscanf(file, "(%lf,%lf) (%lf,%lf) %i", &p1.x, &p1.y, &p2.x, &p2.y, (int *)&type);
419                 obj = (Object *)new Dimension(p1, p2, type);
420         }
421         else if (strcmp(buffer, "CONTAINER") == 0)
422         {
423                 obj = (Object *)new Container();
424 //              objectFileType = OTFContainer;
425         }
426         else if (strcmp(buffer, "ENDCONTAINER") == 0)
427         {
428                 objectFileType = OTFContainerEnd;
429         }
430         else if (strcmp(buffer, "END") == 0)
431         {
432                 objectFileType = OTFEndOfFile;
433         }
434         else
435                 printf("Unknown object type '%s'...\n", buffer);
436
437         if (obj != NULL)
438         {
439                 obj->layer = foundLayer;
440
441                 if (extended && (obj->type != OTContainer))
442                 {
443                         fscanf(file, " (%i, %f, %i)", &obj->color, &obj->thickness, &obj->style);
444                 }
445         }
446
447         return obj;
448 }
449
450
451 /*static*/ bool FileIO::WriteObjectToFile(FILE * file, Object * obj)
452 {
453         // Sanity check
454         if (obj == NULL)
455                 return false;
456
457         switch (obj->type)
458         {
459         case OTLine:
460                 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);
461                 break;
462         case OTCircle:
463                 fprintf(file, "CIRCLE %i (%lf,%lf) %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]);
464                 break;
465         case OTEllipse:
466                 break;
467         case OTArc:
468                 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]);
469                 break;
470         case OTPolygon:
471                 break;
472         case OTDimension:
473 //              fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, dimensionType);
474                 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);
475                 break;
476         case OTSpline:
477                 break;
478         case OTText:
479                 fprintf(file, "TEXT %i (%lf,%lf) \"%s\"", obj->layer, obj->p[0].x, obj->p[0].y, ((Text *)obj)->s.c_str());
480                 break;
481         case OTContainer:
482         {
483                 Container * c = (Container *)obj;
484
485                 if (c->topLevel == false)
486                         fprintf(file, "CONTAINER %i\n", obj->layer);
487
488                 std::vector<void *>::iterator i;
489
490                 for(i=c->objects.begin(); i!=c->objects.end(); i++)
491                         WriteObjectToFile(file, (Object *)*i);
492
493                 if (c->topLevel == false)
494                         fprintf(file, "ENDCONTAINER\n");
495
496                 break;
497         }
498         default:
499                 break;
500         }
501
502         if (obj->type != OTContainer)
503                 fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style);
504
505         return true;
506 }
507