183 print ("Closed %1", name()); |
183 print ("Closed %1", name()); |
184 } |
184 } |
185 |
185 |
186 // ============================================================================= |
186 // ============================================================================= |
187 // |
187 // |
188 LDDocument* findDocument (QString name) |
188 LDDocument* findDocument (String name) |
189 { |
189 { |
190 for (LDDocument * file : g_loadedFiles) |
190 for (LDDocument * file : g_loadedFiles) |
191 if (not file->name().isEmpty() && file->name() == name) |
191 if (not file->name().isEmpty() && file->name() == name) |
192 return file; |
192 return file; |
193 |
193 |
194 return null; |
194 return null; |
195 } |
195 } |
196 |
196 |
197 // ============================================================================= |
197 // ============================================================================= |
198 // |
198 // |
199 QString dirname (QString path) |
199 String dirname (String path) |
200 { |
200 { |
201 long lastpos = path.lastIndexOf (DIRSLASH); |
201 long lastpos = path.lastIndexOf (DIRSLASH); |
202 |
202 |
203 if (lastpos > 0) |
203 if (lastpos > 0) |
204 return path.left (lastpos); |
204 return path.left (lastpos); |
223 return path; |
223 return path; |
224 } |
224 } |
225 |
225 |
226 // ============================================================================= |
226 // ============================================================================= |
227 // |
227 // |
228 static QString findLDrawFilePath (QString relpath, bool subdirs) |
228 static String findLDrawFilePath (String relpath, bool subdirs) |
229 { |
229 { |
230 QString fullPath; |
230 String fullPath; |
231 |
231 |
232 // LDraw models use Windows-style path separators. If we're not on Windows, |
232 // LDraw models use Windows-style path separators. If we're not on Windows, |
233 // replace the path separator now before opening any files. Qt expects |
233 // replace the path separator now before opening any files. Qt expects |
234 // forward-slashes as directory separators. |
234 // forward-slashes as directory separators. |
235 #ifndef WIN32 |
235 #ifndef WIN32 |
236 relpath.replace ("\\", "/"); |
236 relpath.replace ("\\", "/"); |
237 #endif // WIN32 |
237 #endif // WIN32 |
238 |
238 |
239 // Try find it relative to other currently open documents. We want a file |
239 // Try find it relative to other currently open documents. We want a file |
240 // in the immediate vicinity of a current model to override stock LDraw stuff. |
240 // in the immediate vicinity of a current model to override stock LDraw stuff. |
241 QString reltop = basename (dirname (relpath)); |
241 String reltop = basename (dirname (relpath)); |
242 |
242 |
243 for (LDDocument* doc : g_loadedFiles) |
243 for (LDDocument* doc : g_loadedFiles) |
244 { |
244 { |
245 if (doc->fullPath().isEmpty()) |
245 if (doc->fullPath().isEmpty()) |
246 continue; |
246 continue; |
247 |
247 |
248 QString partpath = format ("%1/%2", dirname (doc->fullPath()), relpath); |
248 String partpath = format ("%1/%2", dirname (doc->fullPath()), relpath); |
249 QFile f (partpath); |
249 QFile f (partpath); |
250 |
250 |
251 if (f.exists()) |
251 if (f.exists()) |
252 { |
252 { |
253 // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 |
253 // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 |
254 QString proptop = basename (dirname (partpath)); |
254 String proptop = basename (dirname (partpath)); |
255 |
255 |
256 bool bogus = false; |
256 bool bogus = false; |
257 |
257 |
258 for (QString s : g_specialSubdirectories) |
258 for (String s : g_specialSubdirectories) |
259 { |
259 { |
260 if ((proptop == s && reltop != s) || (reltop == s && proptop != s)) |
260 if ((proptop == s && reltop != s) || (reltop == s && proptop != s)) |
261 { |
261 { |
262 bogus = true; |
262 bogus = true; |
263 break; |
263 break; |
280 |
280 |
281 if (subdirs) |
281 if (subdirs) |
282 { |
282 { |
283 // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's |
283 // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's |
284 // where we download parts from the PT to. |
284 // where we download parts from the PT to. |
285 for (const QString& topdir : QList<QString> ({ io_ldpath, net_downloadpath })) |
285 for (const String& topdir : QList<String> ({ io_ldpath, net_downloadpath })) |
286 { |
286 { |
287 for (const QString& subdir : QList<QString> ({ "parts", "p" })) |
287 for (const String& subdir : QList<String> ({ "parts", "p" })) |
288 { |
288 { |
289 fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); |
289 fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); |
290 |
290 |
291 if (QFile::exists (fullPath)) |
291 if (QFile::exists (fullPath)) |
292 return fullPath; |
292 return fullPath; |
296 |
296 |
297 // Did not find the file. |
297 // Did not find the file. |
298 return ""; |
298 return ""; |
299 } |
299 } |
300 |
300 |
301 QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer) |
301 QFile* openLDrawFile (String relpath, bool subdirs, String* pathpointer) |
302 { |
302 { |
303 print ("Opening %1...\n", relpath); |
303 print ("Opening %1...\n", relpath); |
304 QString path = findLDrawFilePath (relpath, subdirs); |
304 String path = findLDrawFilePath (relpath, subdirs); |
305 |
305 |
306 if (pathpointer != null) |
306 if (pathpointer != null) |
307 *pathpointer = path; |
307 *pathpointer = path; |
308 |
308 |
309 if (path.isEmpty()) |
309 if (path.isEmpty()) |
368 // Parse up to 300 lines per iteration |
368 // Parse up to 300 lines per iteration |
369 int max = i + 300; |
369 int max = i + 300; |
370 |
370 |
371 for (; i < max && i < (int) lines().size(); ++i) |
371 for (; i < max && i < (int) lines().size(); ++i) |
372 { |
372 { |
373 QString line = lines()[i]; |
373 String line = lines()[i]; |
374 |
374 |
375 // Trim the trailing newline |
375 // Trim the trailing newline |
376 QChar c; |
376 QChar c; |
377 |
377 |
378 while (line.endsWith ("\n") || line.endsWith ("\r")) |
378 while (line.endsWith ("\n") || line.endsWith ("\r")) |
445 if (numWarnings) |
445 if (numWarnings) |
446 *numWarnings = 0; |
446 *numWarnings = 0; |
447 |
447 |
448 // Read in the lines |
448 // Read in the lines |
449 while (not fp->atEnd()) |
449 while (not fp->atEnd()) |
450 lines << QString::fromUtf8 (fp->readLine()); |
450 lines << String::fromUtf8 (fp->readLine()); |
451 |
451 |
452 LDFileLoader* loader = new LDFileLoader; |
452 LDFileLoader* loader = new LDFileLoader; |
453 loader->setWarnings (numWarnings); |
453 loader->setWarnings (numWarnings); |
454 loader->setLines (lines); |
454 loader->setLines (lines); |
455 loader->setOnForeground (g_loadingMainFile); |
455 loader->setOnForeground (g_loadingMainFile); |
470 return objs; |
470 return objs; |
471 } |
471 } |
472 |
472 |
473 // ============================================================================= |
473 // ============================================================================= |
474 // |
474 // |
475 LDDocument* openDocument (QString path, bool search, bool implicit) |
475 LDDocument* openDocument (String path, bool search, bool implicit) |
476 { |
476 { |
477 // Convert the file name to lowercase since some parts contain uppercase |
477 // Convert the file name to lowercase since some parts contain uppercase |
478 // file names. I'll assume here that the library will always use lowercase |
478 // file names. I'll assume here that the library will always use lowercase |
479 // file names for the actual parts.. |
479 // file names for the actual parts.. |
480 QFile* fp; |
480 QFile* fp; |
481 QString fullpath; |
481 String fullpath; |
482 |
482 |
483 if (search) |
483 if (search) |
484 fp = openLDrawFile (path.toLower(), true, &fullpath); |
484 fp = openLDrawFile (path.toLower(), true, &fullpath); |
485 else |
485 else |
486 { |
486 { |
541 setlocale (LC_ALL, "C"); |
541 setlocale (LC_ALL, "C"); |
542 |
542 |
543 // If we have unsaved changes, warn and give the option of saving. |
543 // If we have unsaved changes, warn and give the option of saving. |
544 if (hasUnsavedChanges()) |
544 if (hasUnsavedChanges()) |
545 { |
545 { |
546 QString message = format (tr ("There are unsaved changes to %1. Should it be saved?"), |
546 String message = format (tr ("There are unsaved changes to %1. Should it be saved?"), |
547 (name().length() > 0) ? name() : tr ("<anonymous>")); |
547 (name().length() > 0) ? name() : tr ("<anonymous>")); |
548 |
548 |
549 int button = msgbox::question (g_win, tr ("Unsaved Changes"), message, |
549 int button = msgbox::question (g_win, tr ("Unsaved Changes"), message, |
550 (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); |
550 (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); |
551 |
551 |
554 case msgbox::Yes: |
554 case msgbox::Yes: |
555 { |
555 { |
556 // If we don't have a file path yet, we have to ask the user for one. |
556 // If we don't have a file path yet, we have to ask the user for one. |
557 if (name().length() == 0) |
557 if (name().length() == 0) |
558 { |
558 { |
559 QString newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"), |
559 String newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"), |
560 getCurrentDocument()->name(), tr ("LDraw files (*.dat *.ldr)")); |
560 getCurrentDocument()->name(), tr ("LDraw files (*.dat *.ldr)")); |
561 |
561 |
562 if (newpath.length() == 0) |
562 if (newpath.length() == 0) |
563 return false; |
563 return false; |
564 |
564 |
617 g_win->updateActions(); |
617 g_win->updateActions(); |
618 } |
618 } |
619 |
619 |
620 // ============================================================================= |
620 // ============================================================================= |
621 // |
621 // |
622 void addRecentFile (QString path) |
622 void addRecentFile (String path) |
623 { |
623 { |
624 auto& rfiles = io_recentfiles; |
624 auto& rfiles = io_recentfiles; |
625 int idx = rfiles.indexOf (path); |
625 int idx = rfiles.indexOf (path); |
626 |
626 |
627 // If this file already is in the list, pop it out. |
627 // If this file already is in the list, pop it out. |
646 } |
646 } |
647 |
647 |
648 // ============================================================================= |
648 // ============================================================================= |
649 // Open an LDraw file and set it as the main model |
649 // Open an LDraw file and set it as the main model |
650 // ============================================================================= |
650 // ============================================================================= |
651 void openMainFile (QString path) |
651 void openMainFile (String path) |
652 { |
652 { |
653 g_loadingMainFile = true; |
653 g_loadingMainFile = true; |
654 |
654 |
655 // If there's already a file with the same name, this file must replace it. |
655 // If there's already a file with the same name, this file must replace it. |
656 LDDocument* documentToReplace = null; |
656 LDDocument* documentToReplace = null; |
657 QString shortName = LDDocument::shortenName (path); |
657 String shortName = LDDocument::shortenName (path); |
658 |
658 |
659 for (LDDocument* doc : g_loadedFiles) |
659 for (LDDocument* doc : g_loadedFiles) |
660 { |
660 { |
661 if (doc->name() == shortName) |
661 if (doc->name() == shortName) |
662 { |
662 { |
721 g_loadingMainFile = false; |
721 g_loadingMainFile = false; |
722 } |
722 } |
723 |
723 |
724 // ============================================================================= |
724 // ============================================================================= |
725 // |
725 // |
726 bool LDDocument::save (QString savepath) |
726 bool LDDocument::save (String savepath) |
727 { |
727 { |
728 if (not savepath.length()) |
728 if (not savepath.length()) |
729 savepath = fullPath(); |
729 savepath = fullPath(); |
730 |
730 |
731 QFile f (savepath); |
731 QFile f (savepath); |
741 { |
741 { |
742 LDComment* nameComment = static_cast<LDComment*> (nameObject); |
742 LDComment* nameComment = static_cast<LDComment*> (nameObject); |
743 |
743 |
744 if (nameComment->text().left (6) == "Name: ") |
744 if (nameComment->text().left (6) == "Name: ") |
745 { |
745 { |
746 QString newname = shortenName (savepath); |
746 String newname = shortenName (savepath); |
747 nameComment->setText (format ("Name: %1", newname)); |
747 nameComment->setText (format ("Name: %1", newname)); |
748 g_win->buildObjList(); |
748 g_win->buildObjList(); |
749 } |
749 } |
750 } |
750 } |
751 |
751 |
769 |
769 |
770 // ============================================================================= |
770 // ============================================================================= |
771 // |
771 // |
772 class LDParseError : public std::exception |
772 class LDParseError : public std::exception |
773 { |
773 { |
774 PROPERTY (private, QString, error, setError, STOCK_WRITE) |
774 PROPERTY (private, String, error, setError, STOCK_WRITE) |
775 PROPERTY (private, QString, line, setLine, STOCK_WRITE) |
775 PROPERTY (private, String, line, setLine, STOCK_WRITE) |
776 |
776 |
777 public: |
777 public: |
778 LDParseError (QString line, QString a) : |
778 LDParseError (String line, String a) : |
779 m_error (a), |
779 m_error (a), |
780 m_line (line) {} |
780 m_line (line) {} |
781 |
781 |
782 const char* what() const throw() |
782 const char* what() const throw() |
783 { |
783 { |
785 } |
785 } |
786 }; |
786 }; |
787 |
787 |
788 // ============================================================================= |
788 // ============================================================================= |
789 // |
789 // |
790 void checkTokenCount (QString line, const QStringList& tokens, int num) |
790 void checkTokenCount (String line, const QStringList& tokens, int num) |
791 { |
791 { |
792 if (tokens.size() != num) |
792 if (tokens.size() != num) |
793 throw LDParseError (line, format ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); |
793 throw LDParseError (line, format ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); |
794 } |
794 } |
795 |
795 |
796 // ============================================================================= |
796 // ============================================================================= |
797 // |
797 // |
798 void checkTokenNumbers (QString line, const QStringList& tokens, int min, int max) |
798 void checkTokenNumbers (String line, const QStringList& tokens, int min, int max) |
799 { |
799 { |
800 bool ok; |
800 bool ok; |
801 |
801 |
802 // Check scientific notation, e.g. 7.99361e-15 |
802 // Check scientific notation, e.g. 7.99361e-15 |
803 QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+"); |
803 QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+"); |
826 // ============================================================================= |
826 // ============================================================================= |
827 // This is the LDraw code parser function. It takes in a string containing LDraw |
827 // This is the LDraw code parser function. It takes in a string containing LDraw |
828 // code and returns the object parsed from it. parseLine never returns null, |
828 // code and returns the object parsed from it. parseLine never returns null, |
829 // the object will be LDError if it could not be parsed properly. |
829 // the object will be LDError if it could not be parsed properly. |
830 // ============================================================================= |
830 // ============================================================================= |
831 LDObject* parseLine (QString line) |
831 LDObject* parseLine (String line) |
832 { |
832 { |
833 try |
833 try |
834 { |
834 { |
835 QStringList tokens = line.split (" ", QString::SkipEmptyParts); |
835 QStringList tokens = line.split (" ", String::SkipEmptyParts); |
836 |
836 |
837 if (tokens.size() <= 0) |
837 if (tokens.size() <= 0) |
838 { |
838 { |
839 // Line was empty, or only consisted of whitespace |
839 // Line was empty, or only consisted of whitespace |
840 return new LDEmpty; |
840 return new LDEmpty; |
862 // MLCAD is notorious for stuffing these statements in parts it |
862 // MLCAD is notorious for stuffing these statements in parts it |
863 // creates. The above block only handles valid statements, so we |
863 // creates. The above block only handles valid statements, so we |
864 // need to handle MLCAD-style invertnext, clip and noclip separately. |
864 // need to handle MLCAD-style invertnext, clip and noclip separately. |
865 struct |
865 struct |
866 { |
866 { |
867 QString a; |
867 String a; |
868 LDBFC::Statement b; |
868 LDBFC::Statement b; |
869 } BFCData[] = |
869 } BFCData[] = |
870 { |
870 { |
871 { "INVERTNEXT", LDBFC::InvertNext }, |
871 { "INVERTNEXT", LDBFC::InvertNext }, |
872 { "NOCLIP", LDBFC::NoClip }, |
872 { "NOCLIP", LDBFC::NoClip }, |
1011 } |
1011 } |
1012 } |
1012 } |
1013 |
1013 |
1014 // ============================================================================= |
1014 // ============================================================================= |
1015 // |
1015 // |
1016 LDDocument* getDocument (QString filename) |
1016 LDDocument* getDocument (String filename) |
1017 { |
1017 { |
1018 // Try find the file in the list of loaded files |
1018 // Try find the file in the list of loaded files |
1019 LDDocument* doc = findDocument (filename); |
1019 LDDocument* doc = findDocument (filename); |
1020 |
1020 |
1021 // If it's not loaded, try open it |
1021 // If it's not loaded, try open it |
1221 assert (idx >= 0 && idx < m_objects.size()); |
1221 assert (idx >= 0 && idx < m_objects.size()); |
1222 |
1222 |
1223 // Mark this change to history |
1223 // Mark this change to history |
1224 if (not m_history->isIgnoring()) |
1224 if (not m_history->isIgnoring()) |
1225 { |
1225 { |
1226 QString oldcode = getObject (idx)->asText(); |
1226 String oldcode = getObject (idx)->asText(); |
1227 QString newcode = obj->asText(); |
1227 String newcode = obj->asText(); |
1228 *m_history << new EditHistory (idx, oldcode, newcode); |
1228 *m_history << new EditHistory (idx, oldcode, newcode); |
1229 } |
1229 } |
1230 |
1230 |
1231 removeKnownVerticesOf (m_objects[idx]); |
1231 removeKnownVerticesOf (m_objects[idx]); |
1232 m_objects[idx]->unselect(); |
1232 m_objects[idx]->unselect(); |
1276 return !isImplicit() && history()->position() != savePosition(); |
1276 return !isImplicit() && history()->position() != savePosition(); |
1277 } |
1277 } |
1278 |
1278 |
1279 // ============================================================================= |
1279 // ============================================================================= |
1280 // |
1280 // |
1281 QString LDDocument::getDisplayName() |
1281 String LDDocument::getDisplayName() |
1282 { |
1282 { |
1283 if (not name().isEmpty()) |
1283 if (not name().isEmpty()) |
1284 return name(); |
1284 return name(); |
1285 |
1285 |
1286 if (not defaultName().isEmpty()) |
1286 if (not defaultName().isEmpty()) |
1504 addToHistory (new SwapHistory (one->id(), other->id())); |
1504 addToHistory (new SwapHistory (one->id(), other->id())); |
1505 } |
1505 } |
1506 |
1506 |
1507 // ============================================================================= |
1507 // ============================================================================= |
1508 // |
1508 // |
1509 QString LDDocument::shortenName (QString a) // [static] |
1509 String LDDocument::shortenName (String a) // [static] |
1510 { |
1510 { |
1511 QString shortname = basename (a); |
1511 String shortname = basename (a); |
1512 QString topdirname = basename (dirname (a)); |
1512 String topdirname = basename (dirname (a)); |
1513 |
1513 |
1514 if (g_specialSubdirectories.contains (topdirname)) |
1514 if (g_specialSubdirectories.contains (topdirname)) |
1515 shortname.prepend (topdirname + "\\"); |
1515 shortname.prepend (topdirname + "\\"); |
1516 |
1516 |
1517 return shortname; |
1517 return shortname; |