src/file.cpp

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

mercurial