180 log ("Closed %1", getName()); |
180 log ("Closed %1", getName()); |
181 } |
181 } |
182 |
182 |
183 // ============================================================================= |
183 // ============================================================================= |
184 // ----------------------------------------------------------------------------- |
184 // ----------------------------------------------------------------------------- |
185 LDDocument* findDocument (str name) |
185 LDDocument* findDocument (QString name) |
186 { |
186 { |
187 for (LDDocument * file : g_loadedFiles) |
187 for (LDDocument * file : g_loadedFiles) |
188 if (!file->getName().isEmpty() && file->getName() == name) |
188 if (!file->getName().isEmpty() && file->getName() == name) |
189 return file; |
189 return file; |
190 |
190 |
191 return null; |
191 return null; |
192 } |
192 } |
193 |
193 |
194 // ============================================================================= |
194 // ============================================================================= |
195 // ----------------------------------------------------------------------------- |
195 // ----------------------------------------------------------------------------- |
196 str dirname (str path) |
196 QString dirname (QString path) |
197 { |
197 { |
198 long lastpos = path.lastIndexOf (DIRSLASH); |
198 long lastpos = path.lastIndexOf (DIRSLASH); |
199 |
199 |
200 if (lastpos > 0) |
200 if (lastpos > 0) |
201 return path.left (lastpos); |
201 return path.left (lastpos); |
220 return path; |
220 return path; |
221 } |
221 } |
222 |
222 |
223 // ============================================================================= |
223 // ============================================================================= |
224 // ----------------------------------------------------------------------------- |
224 // ----------------------------------------------------------------------------- |
225 File* openLDrawFile (str relpath, bool subdirs) |
225 File* openLDrawFile (QString relpath, bool subdirs) |
226 { |
226 { |
227 log ("Opening %1...\n", relpath); |
227 log ("Opening %1...\n", relpath); |
228 File* f = new File; |
228 File* f = new File; |
229 str fullPath; |
229 QString fullPath; |
230 |
230 |
231 // LDraw models use Windows-style path separators. If we're not on Windows, |
231 // LDraw models use Windows-style path separators. If we're not on Windows, |
232 // replace the path separator now before opening any files. Qt expects |
232 // replace the path separator now before opening any files. Qt expects |
233 // forward-slashes as directory separators. |
233 // forward-slashes as directory separators. |
234 #ifndef WIN32 |
234 #ifndef WIN32 |
235 relpath.replace ("\\", "/"); |
235 relpath.replace ("\\", "/"); |
236 #endif // WIN32 |
236 #endif // WIN32 |
237 |
237 |
238 // Try find it relative to other currently open documents. We want a file |
238 // Try find it relative to other currently open documents. We want a file |
239 // in the immediate vicinity of a current model to override stock LDraw stuff. |
239 // in the immediate vicinity of a current model to override stock LDraw stuff. |
240 str reltop = basename (dirname (relpath)); |
240 QString reltop = basename (dirname (relpath)); |
241 for (LDDocument* doc : g_loadedFiles) |
241 for (LDDocument* doc : g_loadedFiles) |
242 { |
242 { |
243 if (doc->getFullPath().isEmpty()) |
243 if (doc->getFullPath().isEmpty()) |
244 continue; |
244 continue; |
245 |
245 |
246 str partpath = fmt ("%1/%2", dirname (doc->getFullPath()), relpath); |
246 QString partpath = fmt ("%1/%2", dirname (doc->getFullPath()), relpath); |
247 |
247 |
248 if (f->open (partpath, File::Read)) |
248 if (f->open (partpath, File::Read)) |
249 { |
249 { |
250 // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 |
250 // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 |
251 str proptop = basename (dirname (partpath)); |
251 QString proptop = basename (dirname (partpath)); |
252 |
252 |
253 bool bogus = false; |
253 bool bogus = false; |
254 |
254 |
255 for (str s : g_specialSubdirectories) |
255 for (QString s : g_specialSubdirectories) |
256 { |
256 { |
257 if ((proptop == s && reltop != s) || (reltop == s && proptop != s)) |
257 if ((proptop == s && reltop != s) || (reltop == s && proptop != s)) |
258 { |
258 { |
259 bogus = true; |
259 bogus = true; |
260 break; |
260 break; |
277 |
277 |
278 if (subdirs) |
278 if (subdirs) |
279 { |
279 { |
280 // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's |
280 // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's |
281 // where we download parts from the PT to. |
281 // where we download parts from the PT to. |
282 for (const str& topdir : initlist<str> ({ io_ldpath, net_downloadpath })) |
282 for (const QString& topdir : initlist<QString> ({ io_ldpath, net_downloadpath })) |
283 { |
283 { |
284 for (const str& subdir : initlist<str> ({ "parts", "p" })) |
284 for (const QString& subdir : initlist<QString> ({ "parts", "p" })) |
285 { |
285 { |
286 fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); |
286 fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); |
287 |
287 |
288 if (f->open (fullPath, File::Read)) |
288 if (f->open (fullPath, File::Read)) |
289 return f; |
289 return f; |
416 |
416 |
417 // ============================================================================= |
417 // ============================================================================= |
418 // ----------------------------------------------------------------------------- |
418 // ----------------------------------------------------------------------------- |
419 QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok) |
419 QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok) |
420 { |
420 { |
421 QList<str> lines; |
421 QList<QString> lines; |
422 QList<LDObject*> objs; |
422 QList<LDObject*> objs; |
423 |
423 |
424 if (numWarnings) |
424 if (numWarnings) |
425 *numWarnings = 0; |
425 *numWarnings = 0; |
426 |
426 |
427 // Read in the lines |
427 // Read in the lines |
428 for (str line : *f) |
428 for (QString line : *f) |
429 lines << line; |
429 lines << line; |
430 |
430 |
431 LDFileLoader* loader = new LDFileLoader; |
431 LDFileLoader* loader = new LDFileLoader; |
432 loader->setWarnings (numWarnings); |
432 loader->setWarnings (numWarnings); |
433 loader->setLines (lines); |
433 loader->setLines (lines); |
449 return objs; |
449 return objs; |
450 } |
450 } |
451 |
451 |
452 // ============================================================================= |
452 // ============================================================================= |
453 // ----------------------------------------------------------------------------- |
453 // ----------------------------------------------------------------------------- |
454 LDDocument* openDocument (str path, bool search) |
454 LDDocument* openDocument (QString path, bool search) |
455 { |
455 { |
456 // Convert the file name to lowercase since some parts contain uppercase |
456 // Convert the file name to lowercase since some parts contain uppercase |
457 // file names. I'll assume here that the library will always use lowercase |
457 // file names. I'll assume here that the library will always use lowercase |
458 // file names for the actual parts.. |
458 // file names for the actual parts.. |
459 File* f; |
459 File* f; |
516 setlocale (LC_ALL, "C"); |
516 setlocale (LC_ALL, "C"); |
517 |
517 |
518 // If we have unsaved changes, warn and give the option of saving. |
518 // If we have unsaved changes, warn and give the option of saving. |
519 if (hasUnsavedChanges()) |
519 if (hasUnsavedChanges()) |
520 { |
520 { |
521 str message = fmt (tr ("There are unsaved changes to %1. Should it be saved?"), |
521 QString message = fmt (tr ("There are unsaved changes to %1. Should it be saved?"), |
522 (getName().length() > 0) ? getName() : tr ("<anonymous>")); |
522 (getName().length() > 0) ? getName() : tr ("<anonymous>")); |
523 |
523 |
524 int button = msgbox::question (g_win, tr ("Unsaved Changes"), message, |
524 int button = msgbox::question (g_win, tr ("Unsaved Changes"), message, |
525 (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); |
525 (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); |
526 |
526 |
529 case msgbox::Yes: |
529 case msgbox::Yes: |
530 { |
530 { |
531 // If we don't have a file path yet, we have to ask the user for one. |
531 // If we don't have a file path yet, we have to ask the user for one. |
532 if (getName().length() == 0) |
532 if (getName().length() == 0) |
533 { |
533 { |
534 str newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"), |
534 QString newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"), |
535 getCurrentDocument()->getName(), tr ("LDraw files (*.dat *.ldr)")); |
535 getCurrentDocument()->getName(), tr ("LDraw files (*.dat *.ldr)")); |
536 |
536 |
537 if (newpath.length() == 0) |
537 if (newpath.length() == 0) |
538 return false; |
538 return false; |
539 |
539 |
592 g_win->updateActions(); |
592 g_win->updateActions(); |
593 } |
593 } |
594 |
594 |
595 // ============================================================================= |
595 // ============================================================================= |
596 // ----------------------------------------------------------------------------- |
596 // ----------------------------------------------------------------------------- |
597 void addRecentFile (str path) |
597 void addRecentFile (QString path) |
598 { |
598 { |
599 auto& rfiles = io_recentfiles; |
599 auto& rfiles = io_recentfiles; |
600 int idx = rfiles.indexOf (path); |
600 int idx = rfiles.indexOf (path); |
601 |
601 |
602 // If this file already is in the list, pop it out. |
602 // If this file already is in the list, pop it out. |
621 } |
621 } |
622 |
622 |
623 // ============================================================================= |
623 // ============================================================================= |
624 // Open an LDraw file and set it as the main model |
624 // Open an LDraw file and set it as the main model |
625 // ----------------------------------------------------------------------------- |
625 // ----------------------------------------------------------------------------- |
626 void openMainFile (str path) |
626 void openMainFile (QString path) |
627 { |
627 { |
628 g_loadingMainFile = true; |
628 g_loadingMainFile = true; |
629 LDDocument* file = openDocument (path, false); |
629 LDDocument* file = openDocument (path, false); |
630 |
630 |
631 if (!file) |
631 if (!file) |
660 g_loadingMainFile = false; |
660 g_loadingMainFile = false; |
661 } |
661 } |
662 |
662 |
663 // ============================================================================= |
663 // ============================================================================= |
664 // ----------------------------------------------------------------------------- |
664 // ----------------------------------------------------------------------------- |
665 bool LDDocument::save (str savepath) |
665 bool LDDocument::save (QString savepath) |
666 { |
666 { |
667 if (!savepath.length()) |
667 if (!savepath.length()) |
668 savepath = getName(); |
668 savepath = getName(); |
669 |
669 |
670 File f (savepath, File::Write); |
670 File f (savepath, File::Write); |
709 |
709 |
710 // ============================================================================= |
710 // ============================================================================= |
711 // ----------------------------------------------------------------------------- |
711 // ----------------------------------------------------------------------------- |
712 class LDParseError : public std::exception |
712 class LDParseError : public std::exception |
713 { |
713 { |
714 PROPERTY (private, str, Error, STR_OPS, STOCK_WRITE) |
714 PROPERTY (private, QString, Error, STR_OPS, STOCK_WRITE) |
715 PROPERTY (private, str, Line, STR_OPS, STOCK_WRITE) |
715 PROPERTY (private, QString, Line, STR_OPS, STOCK_WRITE) |
716 |
716 |
717 public: |
717 public: |
718 LDParseError (str line, str a) : m_Error (a), m_Line (line) {} |
718 LDParseError (QString line, QString a) : m_Error (a), m_Line (line) {} |
719 |
719 |
720 const char* what() const throw() |
720 const char* what() const throw() |
721 { |
721 { |
722 return getError().toLocal8Bit().constData(); |
722 return getError().toLocal8Bit().constData(); |
723 } |
723 } |
724 }; |
724 }; |
725 |
725 |
726 // ============================================================================= |
726 // ============================================================================= |
727 // ----------------------------------------------------------------------------- |
727 // ----------------------------------------------------------------------------- |
728 void checkTokenCount (str line, const QStringList& tokens, int num) |
728 void checkTokenCount (QString line, const QStringList& tokens, int num) |
729 { |
729 { |
730 if (tokens.size() != num) |
730 if (tokens.size() != num) |
731 throw LDParseError (line, fmt ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); |
731 throw LDParseError (line, fmt ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); |
732 } |
732 } |
733 |
733 |
734 // ============================================================================= |
734 // ============================================================================= |
735 // ----------------------------------------------------------------------------- |
735 // ----------------------------------------------------------------------------- |
736 void checkTokenNumbers (str line, const QStringList& tokens, int min, int max) |
736 void checkTokenNumbers (QString line, const QStringList& tokens, int min, int max) |
737 { |
737 { |
738 bool ok; |
738 bool ok; |
739 |
739 |
740 // Check scientific notation, e.g. 7.99361e-15 |
740 // Check scientific notation, e.g. 7.99361e-15 |
741 QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+"); |
741 QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+"); |
764 // ============================================================================= |
764 // ============================================================================= |
765 // This is the LDraw code parser function. It takes in a string containing LDraw |
765 // This is the LDraw code parser function. It takes in a string containing LDraw |
766 // code and returns the object parsed from it. parseLine never returns null, |
766 // code and returns the object parsed from it. parseLine never returns null, |
767 // the object will be LDError if it could not be parsed properly. |
767 // the object will be LDError if it could not be parsed properly. |
768 // ----------------------------------------------------------------------------- |
768 // ----------------------------------------------------------------------------- |
769 LDObject* parseLine (str line) |
769 LDObject* parseLine (QString line) |
770 { |
770 { |
771 try |
771 try |
772 { |
772 { |
773 QStringList tokens = line.split (" ", str::SkipEmptyParts); |
773 QStringList tokens = line.split (" ", QString::SkipEmptyParts); |
774 |
774 |
775 if (tokens.size() <= 0) |
775 if (tokens.size() <= 0) |
776 { |
776 { |
777 // Line was empty, or only consisted of whitespace |
777 // Line was empty, or only consisted of whitespace |
778 return new LDEmpty; |
778 return new LDEmpty; |
800 // MLCAD is notorious for stuffing these statements in parts it |
800 // MLCAD is notorious for stuffing these statements in parts it |
801 // creates. The above block only handles valid statements, so we |
801 // creates. The above block only handles valid statements, so we |
802 // need to handle MLCAD-style invertnext, clip and noclip separately. |
802 // need to handle MLCAD-style invertnext, clip and noclip separately. |
803 struct |
803 struct |
804 { |
804 { |
805 str a; |
805 QString a; |
806 LDBFC::Type b; |
806 LDBFC::Type b; |
807 } BFCData[] = |
807 } BFCData[] = |
808 { |
808 { |
809 { "INVERTNEXT", LDBFC::InvertNext }, |
809 { "INVERTNEXT", LDBFC::InvertNext }, |
810 { "NOCLIP", LDBFC::NoClip }, |
810 { "NOCLIP", LDBFC::NoClip }, |
952 } |
952 } |
953 } |
953 } |
954 |
954 |
955 // ============================================================================= |
955 // ============================================================================= |
956 // ----------------------------------------------------------------------------- |
956 // ----------------------------------------------------------------------------- |
957 LDDocument* getDocument (str filename) |
957 LDDocument* getDocument (QString filename) |
958 { |
958 { |
959 // Try find the file in the list of loaded files |
959 // Try find the file in the list of loaded files |
960 LDDocument* doc = findDocument (filename); |
960 LDDocument* doc = findDocument (filename); |
961 |
961 |
962 // If it's not loaded, try open it |
962 // If it's not loaded, try open it |
1072 assert (idx >= 0 && idx < m_Objects.size()); |
1072 assert (idx >= 0 && idx < m_Objects.size()); |
1073 |
1073 |
1074 // Mark this change to history |
1074 // Mark this change to history |
1075 if (!m_History->isIgnoring()) |
1075 if (!m_History->isIgnoring()) |
1076 { |
1076 { |
1077 str oldcode = getObject (idx)->raw(); |
1077 QString oldcode = getObject (idx)->raw(); |
1078 str newcode = obj->raw(); |
1078 QString newcode = obj->raw(); |
1079 *m_History << new EditHistory (idx, oldcode, newcode); |
1079 *m_History << new EditHistory (idx, oldcode, newcode); |
1080 } |
1080 } |
1081 |
1081 |
1082 m_Objects[idx]->unselect(); |
1082 m_Objects[idx]->unselect(); |
1083 m_Objects[idx]->setFile (null); |
1083 m_Objects[idx]->setFile (null); |
1119 return !isImplicit() && getHistory()->getPosition() != getSavePosition(); |
1119 return !isImplicit() && getHistory()->getPosition() != getSavePosition(); |
1120 } |
1120 } |
1121 |
1121 |
1122 // ============================================================================= |
1122 // ============================================================================= |
1123 // ----------------------------------------------------------------------------- |
1123 // ----------------------------------------------------------------------------- |
1124 str LDDocument::getDisplayName() |
1124 QString LDDocument::getDisplayName() |
1125 { |
1125 { |
1126 if (!getName().isEmpty()) |
1126 if (!getName().isEmpty()) |
1127 return getName(); |
1127 return getName(); |
1128 |
1128 |
1129 if (!getDefaultName().isEmpty()) |
1129 if (!getDefaultName().isEmpty()) |
1340 addToHistory (new SwapHistory (one->getID(), other->getID())); |
1340 addToHistory (new SwapHistory (one->getID(), other->getID())); |
1341 } |
1341 } |
1342 |
1342 |
1343 // ============================================================================= |
1343 // ============================================================================= |
1344 // ----------------------------------------------------------------------------- |
1344 // ----------------------------------------------------------------------------- |
1345 str LDDocument::shortenName (str a) // [static] |
1345 QString LDDocument::shortenName (QString a) // [static] |
1346 { |
1346 { |
1347 str shortname = basename (a); |
1347 QString shortname = basename (a); |
1348 str topdirname = basename (dirname (a)); |
1348 QString topdirname = basename (dirname (a)); |
1349 |
1349 |
1350 if (g_specialSubdirectories.contains (topdirname)) |
1350 if (g_specialSubdirectories.contains (topdirname)) |
1351 shortname.prepend (topdirname + "\\"); |
1351 shortname.prepend (topdirname + "\\"); |
1352 |
1352 |
1353 return shortname; |
1353 return shortname; |