src/document.cpp

changeset 557
04e140bdeb0b
parent 556
5f4395ec5db0
child 558
5f6e30e0450c
equal deleted inserted replaced
556:5f4395ec5db0 557:04e140bdeb0b
1 /*
2 * LDForge: LDraw parts authoring CAD
3 * Copyright (C) 2013 Santeri Piippo
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 * =====================================================================
18 *
19 * file.cpp: File I/O and management.
20 * - File loading, parsing, manipulation, saving, closing.
21 * - LDraw path verification.
22 */
23
24 #include <QMessageBox>
25 #include <QFileDialog>
26 #include <QDir>
27 #include <QApplication>
28 #include "main.h"
29 #include "config.h"
30 #include "document.h"
31 #include "misc.h"
32 #include "gui.h"
33 #include "history.h"
34 #include "dialogs.h"
35 #include "gldraw.h"
36 #include "moc_document.cpp"
37
38 cfg (String, io_ldpath, "");
39 cfg (List, io_recentfiles, {});
40 extern_cfg (String, net_downloadpath);
41 extern_cfg (Bool, gl_logostuds);
42
43 static bool g_loadingMainFile = false;
44 static const int g_MaxRecentFiles = 5;
45 static bool g_aborted = false;
46 static LDDocument* g_logoedStud = null;
47 static LDDocument* g_logoedStud2 = null;
48
49 LDDocument* LDDocument::m_curdoc = null;
50
51 // =============================================================================
52 // -----------------------------------------------------------------------------
53 namespace LDPaths
54 { static str pathError;
55
56 struct
57 { str LDConfigPath;
58 str partsPath, primsPath;
59 } pathInfo;
60
61 void initPaths()
62 { if (!tryConfigure (io_ldpath))
63 { LDrawPathDialog dlg (false);
64
65 if (!dlg.exec())
66 exit (0);
67
68 io_ldpath = dlg.filename();
69 }
70 }
71
72 bool tryConfigure (str path)
73 { QDir dir;
74
75 if (!dir.cd (path))
76 { pathError = "Directory does not exist.";
77 return false;
78 }
79
80 QStringList mustHave = { "LDConfig.ldr", "parts", "p" };
81 QStringList contents = dir.entryList (mustHave);
82
83 if (contents.size() != mustHave.size())
84 { pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/.";
85 return false;
86 }
87
88 pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path);
89 pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path);
90 pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path);
91
92 return true;
93 }
94
95 // Accessors
96 str getError()
97 { return pathError;
98 }
99
100 str ldconfig()
101 { return pathInfo.LDConfigPath;
102 }
103
104 str prims()
105 { return pathInfo.primsPath;
106 }
107
108 str parts()
109 { return pathInfo.partsPath;
110 }
111 }
112
113 // =============================================================================
114 // -----------------------------------------------------------------------------
115 LDDocument::LDDocument()
116 { setImplicit (true);
117 setSavePosition (-1);
118 setListItem (null);
119 setHistory (new History);
120 m_History->setFile (this);
121 }
122
123 // =============================================================================
124 // -----------------------------------------------------------------------------
125 LDDocument::~LDDocument()
126 { // Clear everything from the model
127 for (LDObject* obj : getObjects())
128 delete obj;
129
130 // Clear the cache as well
131 for (LDObject* obj : getCache())
132 delete obj;
133
134 delete m_History;
135
136 // Remove this file from the list of files
137 g_loadedFiles.removeOne (this);
138
139 // If we just closed the current file, we need to set the current
140 // file as something else.
141 if (this == getCurrentDocument())
142 { bool found = false;
143
144 // Try find an explicitly loaded file - if we can't find one,
145 // we need to create a new file to switch to.
146 for (LDDocument* file : g_loadedFiles)
147 { if (!file->isImplicit())
148 { LDDocument::setCurrent (file);
149 found = true;
150 break;
151 }
152 }
153
154 if (!found)
155 newFile();
156 }
157
158 g_win->updateDocumentList();
159 }
160
161 // =============================================================================
162 // -----------------------------------------------------------------------------
163 LDDocument* findDocument (str name)
164 { for (LDDocument * file : g_loadedFiles)
165 if (!file->getName().isEmpty() && file->getShortName() == name)
166 return file;
167
168 return null;
169 }
170
171 // =============================================================================
172 // -----------------------------------------------------------------------------
173 str dirname (str path)
174 { long lastpos = path.lastIndexOf (DIRSLASH);
175
176 if (lastpos > 0)
177 return path.left (lastpos);
178
179 #ifndef _WIN32
180
181 if (path[0] == DIRSLASH_CHAR)
182 return DIRSLASH;
183
184 #endif // _WIN32
185
186 return "";
187 }
188
189 // =============================================================================
190 // -----------------------------------------------------------------------------
191 str basename (str path)
192 { long lastpos = path.lastIndexOf (DIRSLASH);
193
194 if (lastpos != -1)
195 return path.mid (lastpos + 1);
196
197 return path;
198 }
199
200 // =============================================================================
201 // -----------------------------------------------------------------------------
202 File* openLDrawFile (str relpath, bool subdirs)
203 { log ("Opening %1...\n", relpath);
204 File* f = new File;
205 str fullPath;
206
207 // LDraw models use Windows-style path separators. If we're not on Windows,
208 // replace the path separator now before opening any files.
209 #ifndef WIN32
210 relpath.replace ("\\", "/");
211 #endif // WIN32
212
213 if (getCurrentDocument())
214 { // First, try find the file in the current model's file path. We want a file
215 // in the immediate vicinity of the current model to override stock LDraw stuff.
216 str partpath = fmt ("%1" DIRSLASH "%2", dirname (getCurrentDocument()->getName()), relpath);
217
218 if (f->open (partpath, File::Read))
219 return f;
220 }
221
222 if (f->open (relpath, File::Read))
223 return f;
224
225 // Try with just the LDraw path first
226 fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath);
227
228 if (f->open (fullPath, File::Read))
229 return f;
230
231 if (subdirs)
232 { // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's
233 // where we download parts from the PT to.
234 for (const str& topdir : initlist<str> ({ io_ldpath, net_downloadpath }))
235 { for (const str& subdir : initlist<str> ({ "parts", "p" }))
236 { fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath);
237
238 if (f->open (fullPath, File::Read))
239 return f;
240 }
241 }
242 }
243
244 // Did not find the file.
245 log ("Could not find %1.\n", relpath);
246 delete f;
247 return null;
248 }
249
250 // =============================================================================
251 // -----------------------------------------------------------------------------
252 void LDFileLoader::start()
253 { setDone (false);
254 setProgress (0);
255 setAborted (false);
256
257 if (isOnForeground())
258 { g_aborted = false;
259
260 // Show a progress dialog if we're loading the main document.here so we can
261 // show progress updates and keep the WM posted that we're still here.
262 // Of course we cannot exec() the dialog because then the dialog would
263 // block.
264 dlg = new OpenProgressDialog (g_win);
265 dlg->setNumLines (getLines().size());
266 dlg->setModal (true);
267 dlg->show();
268
269 // Connect the loader in so we can show updates
270 connect (this, SIGNAL (workDone()), dlg, SLOT (accept()));
271 connect (dlg, SIGNAL (rejected()), this, SLOT (abort()));
272 }
273 else
274 dlg = null;
275
276 // Begin working
277 work (0);
278 }
279
280 // =============================================================================
281 // -----------------------------------------------------------------------------
282 void LDFileLoader::work (int i)
283 { // User wishes to abort, so stop here now.
284 if (isAborted())
285 { for (LDObject* obj : m_Objects)
286 delete obj;
287
288 m_Objects.clear();
289 setDone (true);
290 return;
291 }
292
293 // Parse up to 300 lines per iteration
294 int max = i + 300;
295
296 for (; i < max && i < (int) getLines().size(); ++i)
297 { str line = getLines()[i];
298
299 // Trim the trailing newline
300 QChar c;
301
302 while (!line.isEmpty() && ((c = line[line.length() - 1]) == '\n' || c == '\r'))
303 line.chop (1);
304
305 LDObject* obj = parseLine (line);
306
307 // Check for parse errors and warn about tthem
308 if (obj->getType() == LDObject::Error)
309 { log ("Couldn't parse line #%1: %2", getProgress() + 1, static_cast<LDError*> (obj)->reason);
310
311 if (getWarnings() != null)
312 (*getWarnings())++;
313 }
314
315 m_Objects << obj;
316 setProgress (i);
317
318 // If we have a dialog pointer, update the progress now
319 if (isOnForeground())
320 dlg->updateProgress (i);
321 }
322
323 // If we're done now, tell the environment we're done and stop.
324 if (i >= ((int) getLines().size()) - 1)
325 { emit workDone();
326 setDone (true);
327 return;
328 }
329
330 // Otherwise, continue, by recursing back.
331 if (!isDone())
332 { // If we have a dialog to show progress output to, we cannot just call
333 // work() again immediately as the dialog needs some processor cycles as
334 // well. Thus, take a detour through the event loop by using the
335 // meta-object system.
336 //
337 // This terminates the loop here and control goes back to the function
338 // which called the file loader. It will keep processing the event loop
339 // until we're ready (see loadFileContents), thus the event loop will
340 // eventually catch the invokation we throw here and send us back. Though
341 // it's not technically recursion anymore, more like a for loop. :P
342 if (isOnForeground())
343 QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i));
344 else
345 work (i + 1);
346 }
347 }
348
349 // =============================================================================
350 // -----------------------------------------------------------------------------
351 void LDFileLoader::abort()
352 { setAborted (true);
353
354 if (isOnForeground())
355 g_aborted = true;
356 }
357
358 // =============================================================================
359 // -----------------------------------------------------------------------------
360 QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok)
361 { QList<str> lines;
362 QList<LDObject*> objs;
363
364 if (numWarnings)
365 *numWarnings = 0;
366
367 // Calculate the amount of lines
368 for (str line : *f)
369 lines << line;
370
371 f->rewind();
372
373 LDFileLoader* loader = new LDFileLoader;
374 loader->setWarnings (numWarnings);
375 loader->setLines (lines);
376 loader->setOnForeground (g_loadingMainFile);
377 loader->start();
378
379 // After start() returns, if the loader isn't done yet, it's delaying
380 // its next iteration through the event loop. We need to catch this here
381 // by telling the event loop to tick, which will tick the file loader again.
382 // We keep doing this until the file loader is ready.
383 while (loader->isDone() == false)
384 qApp->processEvents();
385
386 // If we wanted the success value, supply that now
387 if (ok)
388 *ok = !loader->isAborted();
389
390 objs = loader->getObjects();
391 return objs;
392 }
393
394 // =============================================================================
395 // -----------------------------------------------------------------------------
396 LDDocument* openDocument (str path, bool search)
397 { // Convert the file name to lowercase since some parts contain uppercase
398 // file names. I'll assume here that the library will always use lowercase
399 // file names for the actual parts..
400 File* f;
401
402 if (search)
403 f = openLDrawFile (path.toLower(), true);
404 else
405 { f = new File (path, File::Read);
406
407 if (!*f)
408 { delete f;
409 return null;
410 }
411 }
412
413 if (!f)
414 return null;
415
416 LDDocument* load = new LDDocument;
417 load->setName (path);
418
419 int numWarnings;
420 bool ok;
421 QList<LDObject*> objs = loadFileContents (f, &numWarnings, &ok);
422
423 if (!ok)
424 return null;
425
426 for (LDObject* obj : objs)
427 load->addObject (obj);
428
429 delete f;
430 g_loadedFiles << load;
431
432 if (g_loadingMainFile)
433 { LDDocument::setCurrent (load);
434 g_win->R()->setFile (load);
435 log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings);
436 }
437
438 return load;
439 }
440
441 // =============================================================================
442 // -----------------------------------------------------------------------------
443 bool LDDocument::isSafeToClose()
444 { typedef QMessageBox msgbox;
445 setlocale (LC_ALL, "C");
446
447 // If we have unsaved changes, warn and give the option of saving.
448 if (hasUnsavedChanges())
449 { str message = fmt ("There are unsaved changes to %1. Should it be saved?",
450 (getName().length() > 0) ? getName() : "<anonymous>");
451
452 int button = msgbox::question (g_win, "Unsaved Changes", message,
453 (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel);
454
455 switch (button)
456 { case msgbox::Yes:
457
458 // If we don't have a file path yet, we have to ask the user for one.
459 if (getName().length() == 0)
460 { str newpath = QFileDialog::getSaveFileName (g_win, "Save As",
461 getCurrentDocument()->getName(), "LDraw files (*.dat *.ldr)");
462
463 if (newpath.length() == 0)
464 return false;
465
466 setName (newpath);
467 }
468
469 if (!save())
470 { message = fmt (QObject::tr ("Failed to save %1: %2\nDo you still want to close?"),
471 getName(), strerror (errno));
472
473 if (msgbox::critical (g_win, "Save Failure", message,
474 (msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No)
475 { return false;
476 }
477 }
478
479 break;
480
481 case msgbox::Cancel:
482 return false;
483
484 default:
485 break;
486 }
487 }
488
489 return true;
490 }
491
492 // =============================================================================
493 // -----------------------------------------------------------------------------
494 void closeAll()
495 { // Remove all loaded files and the objects they contain
496 QList<LDDocument*> files = g_loadedFiles;
497
498 for (LDDocument * file : files)
499 delete file;
500 }
501
502 // =============================================================================
503 // -----------------------------------------------------------------------------
504 void newFile()
505 { // Create a new anonymous file and set it to our current
506 LDDocument* f = new LDDocument;
507 f->setName ("");
508 f->setImplicit (false);
509 g_loadedFiles << f;
510 LDDocument::setCurrent (f);
511 LDDocument::closeInitialFile();
512 g_win->R()->setFile (f);
513 g_win->doFullRefresh();
514 g_win->updateTitle();
515 g_win->updateActions();
516 }
517
518 // =============================================================================
519 // -----------------------------------------------------------------------------
520 void addRecentFile (str path)
521 { alias rfiles = io_recentfiles.value;
522 int idx = rfiles.indexOf (path);
523
524 // If this file already is in the list, pop it out.
525 if (idx != -1)
526 { if (rfiles.size() == 1)
527 return; // only recent file - abort and do nothing
528
529 // Pop it out.
530 rfiles.removeAt (idx);
531 }
532
533 // If there's too many recent files, drop one out.
534 while (rfiles.size() > (g_MaxRecentFiles - 1))
535 rfiles.removeAt (0);
536
537 // Add the file
538 rfiles << path;
539
540 Config::save();
541 g_win->updateRecentFilesMenu();
542 }
543
544 // =============================================================================
545 // Open an LDraw file and set it as the main model
546 // -----------------------------------------------------------------------------
547 void openMainFile (str path)
548 { g_loadingMainFile = true;
549 LDDocument* file = openDocument (path, false);
550
551 if (!file)
552 { // Loading failed, thus drop down to a new file since we
553 // closed everything prior.
554 newFile();
555
556 if (!g_aborted)
557 { // Tell the user loading failed.
558 setlocale (LC_ALL, "C");
559 critical (fmt (QObject::tr ("Failed to open %1: %2"), path, strerror (errno)));
560 }
561
562 g_loadingMainFile = false;
563 return;
564 }
565
566 file->setImplicit (false);
567
568 // If we have an anonymous, unchanged file open as the only open file
569 // (aside of the one we just opened), close it now.
570 LDDocument::closeInitialFile();
571
572 // Rebuild the object tree view now.
573 LDDocument::setCurrent (file);
574 g_win->doFullRefresh();
575
576 // Add it to the recent files list.
577 addRecentFile (path);
578 g_loadingMainFile = false;
579 }
580
581 // =============================================================================
582 // -----------------------------------------------------------------------------
583 bool LDDocument::save (str savepath)
584 { if (!savepath.length())
585 savepath = getName();
586
587 File f (savepath, File::Write);
588
589 if (!f)
590 return false;
591
592 // If the second object in the list holds the file name, update that now.
593 // Only do this if the file is explicitly open. If it's saved into a directory
594 // called "s" or "48", prepend that into the name.
595 LDComment* fpathComment = null;
596 LDObject* first = getObject (1);
597
598 if (!isImplicit() && first != null && first->getType() == LDObject::Comment)
599 { fpathComment = static_cast<LDComment*> (first);
600
601 if (fpathComment->text.left (6) == "Name: ")
602 { str newname;
603 str dir = basename (dirname (savepath));
604
605 if (dir == "s" || dir == "48")
606 newname = dir + "\\";
607
608 newname += basename (savepath);
609 fpathComment->text = fmt ("Name: %1", newname);
610 g_win->buildObjList();
611 }
612 }
613
614 // File is open, now save the model to it. Note that LDraw requires files to
615 // have DOS line endings, so we terminate the lines with \r\n.
616 for (LDObject* obj : getObjects())
617 f.write (obj->raw() + "\r\n");
618
619 // File is saved, now clean up.
620 f.close();
621
622 // We have successfully saved, update the save position now.
623 setSavePosition (getHistory()->getPosition());
624 setName (savepath);
625
626 g_win->updateDocumentListItem (this);
627 g_win->updateTitle();
628 return true;
629 }
630
631 // =============================================================================
632 // -----------------------------------------------------------------------------
633 #define CHECK_TOKEN_COUNT(N) \
634 if (tokens.size() != N) \
635 return new LDError (line, "Bad amount of tokens");
636
637 #define CHECK_TOKEN_NUMBERS(MIN, MAX) \
638 for (int i = MIN; i <= MAX; ++i) \
639 if (!numeric (tokens[i])) \
640 return new LDError (line, fmt ("Token #%1 was `%2`, expected a number", (i + 1), tokens[i]));
641
642 // =============================================================================
643 // -----------------------------------------------------------------------------
644 static vertex parseVertex (QStringList& s, const int n)
645 { vertex v;
646
647 for (const Axis ax : g_Axes)
648 v[ax] = s[n + ax].toDouble();
649
650 return v;
651 }
652
653 // =============================================================================
654 // This is the LDraw code parser function. It takes in a string containing LDraw
655 // code and returns the object parsed from it. parseLine never returns null,
656 // the object will be LDError if it could not be parsed properly.
657 // -----------------------------------------------------------------------------
658 LDObject* parseLine (str line)
659 { QStringList tokens = line.split (" ", str::SkipEmptyParts);
660
661 if (tokens.size() <= 0)
662 { // Line was empty, or only consisted of whitespace
663 return new LDEmpty;
664 }
665
666 if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false)
667 return new LDError (line, "Illogical line code");
668
669 int num = tokens[0][0].digitValue();
670
671 switch (num)
672 { case 0:
673 { // Comment
674 str comm = line.mid (line.indexOf ("0") + 1);
675
676 // Remove any leading whitespace
677 while (comm[0] == ' ')
678 comm.remove (0, 1);
679
680 // Handle BFC statements
681 if (tokens.size() > 2 && tokens[1] == "BFC")
682 { for (int i = 0; i < LDBFC::NumStatements; ++i)
683 if (comm == fmt ("BFC %1", LDBFC::statements [i]))
684 return new LDBFC ( (LDBFC::Type) i);
685
686 // MLCAD is notorious for stuffing these statements in parts it
687 // creates. The above block only handles valid statements, so we
688 // need to handle MLCAD-style invertnext, clip and noclip separately.
689 struct
690 { const char* a;
691 LDBFC::Type b;
692 } BFCData[] =
693 { { "INVERTNEXT", LDBFC::InvertNext },
694 { "NOCLIP", LDBFC::NoClip },
695 { "CLIP", LDBFC::Clip }
696 };
697
698 for (const auto & i : BFCData)
699 if (comm == fmt ("BFC CERTIFY %1", i.a))
700 return new LDBFC (i.b);
701 }
702
703 if (tokens.size() > 2 && tokens[1] == "!LDFORGE")
704 { // Handle LDForge-specific types, they're embedded into comments too
705 if (tokens[2] == "VERTEX")
706 { // Vertex (0 !LDFORGE VERTEX)
707 CHECK_TOKEN_COUNT (7)
708 CHECK_TOKEN_NUMBERS (3, 6)
709
710 LDVertex* obj = new LDVertex;
711 obj->setColor (tokens[3].toLong());
712
713 for (const Axis ax : g_Axes)
714 obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6
715
716 return obj;
717 } elif (tokens[2] == "OVERLAY")
718
719 { CHECK_TOKEN_COUNT (9);
720 CHECK_TOKEN_NUMBERS (5, 8)
721
722 LDOverlay* obj = new LDOverlay;
723 obj->setFileName (tokens[3]);
724 obj->setCamera (tokens[4].toLong());
725 obj->setX (tokens[5].toLong());
726 obj->setY (tokens[6].toLong());
727 obj->setWidth (tokens[7].toLong());
728 obj->setHeight (tokens[8].toLong());
729 return obj;
730 }
731 }
732
733 // Just a regular comment:
734 LDComment* obj = new LDComment;
735 obj->text = comm;
736 return obj;
737 }
738
739 case 1:
740 { // Subfile
741 CHECK_TOKEN_COUNT (15)
742 CHECK_TOKEN_NUMBERS (1, 13)
743
744 // Try open the file. Disable g_loadingMainFile temporarily since we're
745 // not loading the main file now, but the subfile in question.
746 bool tmp = g_loadingMainFile;
747 g_loadingMainFile = false;
748 LDDocument* load = getDocument (tokens[14]);
749 g_loadingMainFile = tmp;
750
751 // If we cannot open the file, mark it an error
752 if (!load)
753 { LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14]));
754 obj->setFileReferenced (tokens[14]);
755 return obj;
756 }
757
758 LDSubfile* obj = new LDSubfile;
759 obj->setColor (tokens[1].toLong());
760 obj->setPosition (parseVertex (tokens, 2)); // 2 - 4
761
762 matrix transform;
763
764 for (int i = 0; i < 9; ++i)
765 transform[i] = tokens[i + 5].toDouble(); // 5 - 13
766
767 obj->setTransform (transform);
768 obj->setFileInfo (load);
769 return obj;
770 }
771
772 case 2:
773 { CHECK_TOKEN_COUNT (8)
774 CHECK_TOKEN_NUMBERS (1, 7)
775
776 // Line
777 LDLine* obj = new LDLine;
778 obj->setColor (tokens[1].toLong());
779
780 for (int i = 0; i < 2; ++i)
781 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 7
782
783 return obj;
784 }
785
786 case 3:
787 { CHECK_TOKEN_COUNT (11)
788 CHECK_TOKEN_NUMBERS (1, 10)
789
790 // Triangle
791 LDTriangle* obj = new LDTriangle;
792 obj->setColor (tokens[1].toLong());
793
794 for (int i = 0; i < 3; ++i)
795 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 10
796
797 return obj;
798 }
799
800 case 4:
801 case 5:
802 { CHECK_TOKEN_COUNT (14)
803 CHECK_TOKEN_NUMBERS (1, 13)
804
805 // Quadrilateral / Conditional line
806 LDObject* obj;
807
808 if (num == 4)
809 obj = new LDQuad;
810 else
811 obj = new LDCondLine;
812
813 obj->setColor (tokens[1].toLong());
814
815 for (int i = 0; i < 4; ++i)
816 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13
817
818 return obj;
819 }
820
821 default: // Strange line we couldn't parse
822 return new LDError (line, "Unknown line code number");
823 }
824 }
825
826 // =============================================================================
827 // -----------------------------------------------------------------------------
828 LDDocument* getDocument (str filename)
829 { // Try find the file in the list of loaded files
830 LDDocument* doc = findDocument (filename);
831
832 // If it's not loaded, try open it
833 if (!doc)
834 doc = openDocument (filename, true);
835
836 return doc;
837 }
838
839 // =============================================================================
840 // -----------------------------------------------------------------------------
841 void reloadAllSubfiles()
842 { if (!getCurrentDocument())
843 return;
844
845 g_loadedFiles.clear();
846 g_loadedFiles << getCurrentDocument();
847
848 // Go through all objects in the current file and reload the subfiles
849 for (LDObject* obj : getCurrentDocument()->getObjects())
850 { if (obj->getType() == LDObject::Subfile)
851 { LDSubfile* ref = static_cast<LDSubfile*> (obj);
852 LDDocument* fileInfo = getDocument (ref->getFileInfo()->getName());
853
854 if (fileInfo)
855 ref->setFileInfo (fileInfo);
856 else
857 ref->replace (new LDError (ref->raw(), "Could not open referred file"));
858 }
859
860 // Reparse gibberish files. It could be that they are invalid because
861 // of loading errors. Circumstances may be different now.
862 if (obj->getType() == LDObject::Error)
863 obj->replace (parseLine (static_cast<LDError*> (obj)->contents));
864 }
865
866 // Close all files left unused
867 LDDocument::closeUnused();
868 }
869
870 // =============================================================================
871 // -----------------------------------------------------------------------------
872 int LDDocument::addObject (LDObject* obj)
873 { getHistory()->add (new AddHistory (getObjects().size(), obj));
874 m_Objects << obj;
875
876 if (obj->getType() == LDObject::Vertex)
877 m_Vertices << obj;
878
879 obj->setFile (this);
880 return getObjectCount() - 1;
881 }
882
883 // =============================================================================
884 // -----------------------------------------------------------------------------
885 void LDDocument::addObjects (const QList<LDObject*> objs)
886 { for (LDObject * obj : objs)
887 if (obj)
888 addObject (obj);
889 }
890
891 // =============================================================================
892 // -----------------------------------------------------------------------------
893 void LDDocument::insertObj (int pos, LDObject* obj)
894 { getHistory()->add (new AddHistory (pos, obj));
895 m_Objects.insert (pos, obj);
896 obj->setFile (this);
897 }
898
899 // =============================================================================
900 // -----------------------------------------------------------------------------
901 void LDDocument::forgetObject (LDObject* obj)
902 { int idx = obj->getIndex();
903 getHistory()->add (new DelHistory (idx, obj));
904 m_Objects.removeAt (idx);
905 obj->setFile (null);
906 }
907
908 // =============================================================================
909 // -----------------------------------------------------------------------------
910 bool safeToCloseAll()
911 { for (LDDocument* f : g_loadedFiles)
912 if (!f->isSafeToClose())
913 return false;
914
915 return true;
916 }
917
918 // =============================================================================
919 // -----------------------------------------------------------------------------
920 void LDDocument::setObject (int idx, LDObject* obj)
921 { assert (idx < m_Objects.size());
922
923 // Mark this change to history
924 str oldcode = getObject (idx)->raw();
925 str newcode = obj->raw();
926 *m_History << new EditHistory (idx, oldcode, newcode);
927
928 obj->setFile (this);
929 m_Objects[idx] = obj;
930 }
931
932 // =============================================================================
933 // -----------------------------------------------------------------------------
934 static QList<LDDocument*> getFilesUsed (LDDocument* node)
935 { QList<LDDocument*> filesUsed;
936
937 for (LDObject* obj : node->getObjects())
938 { if (obj->getType() != LDObject::Subfile)
939 continue;
940
941 LDSubfile* ref = static_cast<LDSubfile*> (obj);
942 filesUsed << ref->getFileInfo();
943 filesUsed << getFilesUsed (ref->getFileInfo());
944 }
945
946 return filesUsed;
947 }
948
949 // =============================================================================
950 // Find out which files are unused and close them.
951 // -----------------------------------------------------------------------------
952 void LDDocument::closeUnused()
953 { QList<LDDocument*> filesUsed = getFilesUsed (getCurrentDocument());
954
955 // Anything that's explicitly opened must not be closed
956 for (LDDocument* file : g_loadedFiles)
957 if (!file->isImplicit())
958 filesUsed << file;
959
960 // Remove duplicated entries
961 removeDuplicates (filesUsed);
962
963 // Close all open files that aren't in filesUsed
964 for (LDDocument* file : g_loadedFiles)
965 { bool isused = false;
966
967 for (LDDocument* usedFile : filesUsed)
968 { if (file == usedFile)
969 { isused = true;
970 break;
971 }
972 }
973
974 if (!isused)
975 delete file;
976 }
977
978 g_loadedFiles.clear();
979 g_loadedFiles << filesUsed;
980 }
981
982 // =============================================================================
983 // -----------------------------------------------------------------------------
984 LDObject* LDDocument::getObject (int pos) const
985 { if (m_Objects.size() <= pos)
986 return null;
987
988 return m_Objects[pos];
989 }
990
991 // =============================================================================
992 // -----------------------------------------------------------------------------
993 int LDDocument::getObjectCount() const
994 { return getObjects().size();
995 }
996
997 // =============================================================================
998 // -----------------------------------------------------------------------------
999 bool LDDocument::hasUnsavedChanges() const
1000 { return !isImplicit() && getHistory()->getPosition() != getSavePosition();
1001 }
1002
1003 // =============================================================================
1004 // -----------------------------------------------------------------------------
1005 str LDDocument::getShortName()
1006 { if (!getName().isEmpty())
1007 return basename (getName());
1008
1009 if (!getDefaultName().isEmpty())
1010 return getDefaultName();
1011
1012 return tr ("<anonymous>");
1013 }
1014
1015 // =============================================================================
1016 // -----------------------------------------------------------------------------
1017 QList<LDObject*> LDDocument::inlineContents (LDSubfile::InlineFlags flags)
1018 { // Possibly substitute with logoed studs:
1019 // stud.dat -> stud-logo.dat
1020 // stud2.dat -> stud-logo2.dat
1021 if (gl_logostuds && (flags & LDSubfile::RendererInline))
1022 { if (getName() == "stud.dat" && g_logoedStud)
1023 return g_logoedStud->inlineContents (flags);
1024
1025 elif (getName() == "stud2.dat" && g_logoedStud2)
1026 return g_logoedStud2->inlineContents (flags);
1027 }
1028
1029 QList<LDObject*> objs, objcache;
1030
1031 bool deep = flags & LDSubfile::DeepInline,
1032 doCache = flags & LDSubfile::CacheInline;
1033
1034 // If we have this cached, just clone that
1035 if (deep && getCache().size())
1036 { for (LDObject* obj : getCache())
1037 objs << obj->clone();
1038 }
1039 else
1040 { if (!deep)
1041 doCache = false;
1042
1043 for (LDObject* obj : getObjects())
1044 { // Skip those without scemantic meaning
1045 if (!obj->isScemantic())
1046 continue;
1047
1048 // Got another sub-file reference, inline it if we're deep-inlining. If not,
1049 // just add it into the objects normally. Also, we only cache immediate
1050 // subfiles and this is not one. Yay, recursion!
1051 if (deep && obj->getType() == LDObject::Subfile)
1052 { LDSubfile* ref = static_cast<LDSubfile*> (obj);
1053
1054 // We only want to cache immediate subfiles, so shed the caching
1055 // flag when recursing deeper in hierarchy.
1056 QList<LDObject*> otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline));
1057
1058 for (LDObject * otherobj : otherobjs)
1059 { // Cache this object, if desired
1060 if (doCache)
1061 objcache << otherobj->clone();
1062
1063 objs << otherobj;
1064 }
1065 }
1066 else
1067 { if (doCache)
1068 objcache << obj->clone();
1069
1070 objs << obj->clone();
1071 }
1072 }
1073
1074 if (doCache)
1075 setCache (objcache);
1076 }
1077
1078 return objs;
1079 }
1080
1081 // =============================================================================
1082 // -----------------------------------------------------------------------------
1083 LDDocument* LDDocument::current()
1084 { return m_curdoc;
1085 }
1086
1087 // =============================================================================
1088 // Sets the given file as the current one on display. At some point in time this
1089 // was an operation completely unheard of. ;)
1090 //
1091 // FIXME: f can be temporarily null. This probably should not be the case.
1092 // -----------------------------------------------------------------------------
1093 void LDDocument::setCurrent (LDDocument* f)
1094 { // Implicit files were loaded for caching purposes and must never be set
1095 // current.
1096 if (f && f->isImplicit())
1097 return;
1098
1099 m_curdoc = f;
1100
1101 if (g_win && f)
1102 { // A ton of stuff needs to be updated
1103 g_win->updateDocumentListItem (f);
1104 g_win->buildObjList();
1105 g_win->updateTitle();
1106 g_win->R()->setFile (f);
1107 g_win->R()->resetAllAngles();
1108 g_win->R()->repaint();
1109
1110 log ("Changed file to %1", f->getShortName());
1111 }
1112 }
1113
1114 // =============================================================================
1115 // -----------------------------------------------------------------------------
1116 int LDDocument::countExplicitFiles()
1117 { int count = 0;
1118
1119 for (LDDocument* f : g_loadedFiles)
1120 if (f->isImplicit() == false)
1121 count++;
1122
1123 return count;
1124 }
1125
1126 // =============================================================================
1127 // This little beauty closes the initial file that was open at first when opening
1128 // a new file over it.
1129 // -----------------------------------------------------------------------------
1130 void LDDocument::closeInitialFile()
1131 { if (
1132 countExplicitFiles() == 2 &&
1133 g_loadedFiles[0]->getName() == "" &&
1134 !g_loadedFiles[0]->hasUnsavedChanges()
1135 )
1136 delete g_loadedFiles[0];
1137 }
1138
1139 // =============================================================================
1140 // -----------------------------------------------------------------------------
1141 void loadLogoedStuds()
1142 { log ("Loading logoed studs...\n");
1143
1144 delete g_logoedStud;
1145 delete g_logoedStud2;
1146
1147 g_logoedStud = openDocument ("stud-logo.dat", true);
1148 g_logoedStud2 = openDocument ("stud2-logo.dat", true);
1149 }
1150
1151 // =============================================================================
1152 // -----------------------------------------------------------------------------
1153 void LDDocument::addToSelection (LDObject* obj) // [protected]
1154 { if (obj->isSelected())
1155 return;
1156
1157 assert (obj->getFile() == this);
1158 m_sel << obj;
1159 obj->setSelected (true);
1160 }
1161
1162 // =============================================================================
1163 // -----------------------------------------------------------------------------
1164 void LDDocument::removeFromSelection (LDObject* obj) // [protected]
1165 { if (!obj->isSelected())
1166 return;
1167
1168 assert (obj->getFile() == this);
1169 m_sel.removeOne (obj);
1170 obj->setSelected (false);
1171 }
1172
1173 // =============================================================================
1174 // -----------------------------------------------------------------------------
1175 void LDDocument::clearSelection()
1176 { for (LDObject* obj : m_sel)
1177 removeFromSelection (obj);
1178
1179 assert (m_sel.isEmpty());
1180 }
1181
1182 // =============================================================================
1183 // -----------------------------------------------------------------------------
1184 const QList<LDObject*>& LDDocument::getSelection() const
1185 { return m_sel;
1186 }

mercurial