87 { |
87 { |
88 pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/."; |
88 pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/."; |
89 return false; |
89 return false; |
90 } |
90 } |
91 |
91 |
92 pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path); |
92 pathInfo.partsPath = format ("%1" DIRSLASH "parts", path); |
93 pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path); |
93 pathInfo.LDConfigPath = format ("%1" DIRSLASH "LDConfig.ldr", path); |
94 pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path); |
94 pathInfo.primsPath = format ("%1" DIRSLASH "p", path); |
95 |
95 |
96 return true; |
96 return true; |
97 } |
97 } |
98 |
98 |
99 // Accessors |
99 // Accessors |
177 g_logoedStud = null; |
177 g_logoedStud = null; |
178 elif (this == g_logoedStud2) |
178 elif (this == g_logoedStud2) |
179 g_logoedStud2 = null; |
179 g_logoedStud2 = null; |
180 |
180 |
181 g_win->updateDocumentList(); |
181 g_win->updateDocumentList(); |
182 log ("Closed %1", name()); |
182 print ("Closed %1", name()); |
183 } |
183 } |
184 |
184 |
185 // ============================================================================= |
185 // ============================================================================= |
186 // |
186 // |
187 LDDocument* findDocument (QString name) |
187 LDDocument* findDocument (QString name) |
242 for (LDDocument* doc : g_loadedFiles) |
242 for (LDDocument* doc : g_loadedFiles) |
243 { |
243 { |
244 if (doc->fullPath().isEmpty()) |
244 if (doc->fullPath().isEmpty()) |
245 continue; |
245 continue; |
246 |
246 |
247 QString partpath = fmt ("%1/%2", dirname (doc->fullPath()), relpath); |
247 QString partpath = format ("%1/%2", dirname (doc->fullPath()), relpath); |
248 QFile f (partpath); |
248 QFile f (partpath); |
249 |
249 |
250 if (f.exists()) |
250 if (f.exists()) |
251 { |
251 { |
252 // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 |
252 // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 |
270 |
270 |
271 if (QFile::exists (relpath)) |
271 if (QFile::exists (relpath)) |
272 return relpath; |
272 return relpath; |
273 |
273 |
274 // Try with just the LDraw path first |
274 // Try with just the LDraw path first |
275 fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath); |
275 fullPath = format ("%1" DIRSLASH "%2", io_ldpath, relpath); |
276 |
276 |
277 if (QFile::exists (fullPath)) |
277 if (QFile::exists (fullPath)) |
278 return fullPath; |
278 return fullPath; |
279 |
279 |
280 if (subdirs) |
280 if (subdirs) |
283 // where we download parts from the PT to. |
283 // where we download parts from the PT to. |
284 for (const QString& topdir : initlist<QString> ({ io_ldpath, net_downloadpath })) |
284 for (const QString& topdir : initlist<QString> ({ io_ldpath, net_downloadpath })) |
285 { |
285 { |
286 for (const QString& subdir : initlist<QString> ({ "parts", "p" })) |
286 for (const QString& subdir : initlist<QString> ({ "parts", "p" })) |
287 { |
287 { |
288 fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); |
288 fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); |
289 |
289 |
290 if (QFile::exists (fullPath)) |
290 if (QFile::exists (fullPath)) |
291 return fullPath; |
291 return fullPath; |
292 } |
292 } |
293 } |
293 } |
297 return ""; |
297 return ""; |
298 } |
298 } |
299 |
299 |
300 QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer) |
300 QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer) |
301 { |
301 { |
302 log ("Opening %1...\n", relpath); |
302 print ("Opening %1...\n", relpath); |
303 QString path = findLDrawFilePath (relpath, subdirs); |
303 QString path = findLDrawFilePath (relpath, subdirs); |
304 |
304 |
305 if (pathpointer != null) |
305 if (pathpointer != null) |
306 *pathpointer = path; |
306 *pathpointer = path; |
307 |
307 |
380 LDObject* obj = parseLine (line); |
380 LDObject* obj = parseLine (line); |
381 |
381 |
382 // Check for parse errors and warn about tthem |
382 // Check for parse errors and warn about tthem |
383 if (obj->type() == LDObject::EError) |
383 if (obj->type() == LDObject::EError) |
384 { |
384 { |
385 log ("Couldn't parse line #%1: %2", progress() + 1, static_cast<LDError*> (obj)->reason()); |
385 print ("Couldn't parse line #%1: %2", progress() + 1, static_cast<LDError*> (obj)->reason()); |
386 |
386 |
387 if (warnings() != null) |
387 if (warnings() != null) |
388 (*warnings())++; |
388 (*warnings())++; |
389 } |
389 } |
390 |
390 |
497 return null; |
497 return null; |
498 |
498 |
499 LDDocument* load = new LDDocument; |
499 LDDocument* load = new LDDocument; |
500 load->setFullPath (fullpath); |
500 load->setFullPath (fullpath); |
501 load->setName (LDDocument::shortenName (load->fullPath())); |
501 load->setName (LDDocument::shortenName (load->fullPath())); |
502 dlog ("name: %1 (%2)", load->name(), load->fullPath()); |
502 dprint ("name: %1 (%2)", load->name(), load->fullPath()); |
503 g_loadedFiles << load; |
503 g_loadedFiles << load; |
504 |
504 |
505 // Don't take the file loading as actual edits to the file |
505 // Don't take the file loading as actual edits to the file |
506 load->history()->setIgnoring (true); |
506 load->history()->setIgnoring (true); |
507 |
507 |
522 |
522 |
523 if (g_loadingMainFile) |
523 if (g_loadingMainFile) |
524 { |
524 { |
525 LDDocument::setCurrent (load); |
525 LDDocument::setCurrent (load); |
526 g_win->R()->setDocument (load); |
526 g_win->R()->setDocument (load); |
527 log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); |
527 print (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); |
528 } |
528 } |
529 |
529 |
530 load->history()->setIgnoring (false); |
530 load->history()->setIgnoring (false); |
531 return load; |
531 return load; |
532 } |
532 } |
539 setlocale (LC_ALL, "C"); |
539 setlocale (LC_ALL, "C"); |
540 |
540 |
541 // If we have unsaved changes, warn and give the option of saving. |
541 // If we have unsaved changes, warn and give the option of saving. |
542 if (hasUnsavedChanges()) |
542 if (hasUnsavedChanges()) |
543 { |
543 { |
544 QString message = fmt (tr ("There are unsaved changes to %1. Should it be saved?"), |
544 QString message = format (tr ("There are unsaved changes to %1. Should it be saved?"), |
545 (name().length() > 0) ? name() : tr ("<anonymous>")); |
545 (name().length() > 0) ? name() : tr ("<anonymous>")); |
546 |
546 |
547 int button = msgbox::question (g_win, tr ("Unsaved Changes"), message, |
547 int button = msgbox::question (g_win, tr ("Unsaved Changes"), message, |
548 (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); |
548 (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); |
549 |
549 |
563 setName (newpath); |
563 setName (newpath); |
564 } |
564 } |
565 |
565 |
566 if (!save()) |
566 if (!save()) |
567 { |
567 { |
568 message = fmt (tr ("Failed to save %1 (%2)\nDo you still want to close?"), |
568 message = format (tr ("Failed to save %1 (%2)\nDo you still want to close?"), |
569 name(), strerror (errno)); |
569 name(), strerror (errno)); |
570 |
570 |
571 if (msgbox::critical (g_win, tr ("Save Failure"), message, |
571 if (msgbox::critical (g_win, tr ("Save Failure"), message, |
572 (msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No) |
572 (msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No) |
573 { |
573 { |
681 |
681 |
682 if (!g_aborted) |
682 if (!g_aborted) |
683 { |
683 { |
684 // Tell the user loading failed. |
684 // Tell the user loading failed. |
685 setlocale (LC_ALL, "C"); |
685 setlocale (LC_ALL, "C"); |
686 critical (fmt (QObject::tr ("Failed to open %1: %2"), path, strerror (errno))); |
686 critical (format (QObject::tr ("Failed to open %1: %2"), path, strerror (errno))); |
687 } |
687 } |
688 |
688 |
689 g_loadingMainFile = false; |
689 g_loadingMainFile = false; |
690 return; |
690 return; |
691 } |
691 } |
694 |
694 |
695 // Replace references to the old file with the new file. |
695 // Replace references to the old file with the new file. |
696 if (documentToReplace != null) |
696 if (documentToReplace != null) |
697 { |
697 { |
698 for (LDDocumentPointer* ptr : documentToReplace->references()) |
698 for (LDDocumentPointer* ptr : documentToReplace->references()) |
699 { dlog ("ptr: %1 (%2)\n", |
699 { dprint ("ptr: %1 (%2)\n", |
700 ptr, ptr->pointer() ? ptr->pointer()->name() : "<null>"); |
700 ptr, ptr->pointer() ? ptr->pointer()->name() : "<null>"); |
701 |
701 |
702 *ptr = file; |
702 *ptr = file; |
703 } |
703 } |
704 |
704 |
740 LDComment* nameComment = static_cast<LDComment*> (nameObject); |
740 LDComment* nameComment = static_cast<LDComment*> (nameObject); |
741 |
741 |
742 if (nameComment->text().left (6) == "Name: ") |
742 if (nameComment->text().left (6) == "Name: ") |
743 { |
743 { |
744 QString newname = shortenName (savepath); |
744 QString newname = shortenName (savepath); |
745 nameComment->setText (fmt ("Name: %1", newname)); |
745 nameComment->setText (format ("Name: %1", newname)); |
746 g_win->buildObjList(); |
746 g_win->buildObjList(); |
747 } |
747 } |
748 } |
748 } |
749 |
749 |
750 // File is open, now save the model to it. Note that LDraw requires files to |
750 // File is open, now save the model to it. Note that LDraw requires files to |
786 // ============================================================================= |
786 // ============================================================================= |
787 // |
787 // |
788 void checkTokenCount (QString line, const QStringList& tokens, int num) |
788 void checkTokenCount (QString line, const QStringList& tokens, int num) |
789 { |
789 { |
790 if (tokens.size() != num) |
790 if (tokens.size() != num) |
791 throw LDParseError (line, fmt ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); |
791 throw LDParseError (line, format ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); |
792 } |
792 } |
793 |
793 |
794 // ============================================================================= |
794 // ============================================================================= |
795 // |
795 // |
796 void checkTokenNumbers (QString line, const QStringList& tokens, int min, int max) |
796 void checkTokenNumbers (QString line, const QStringList& tokens, int min, int max) |
803 for (int i = min; i <= max; ++i) |
803 for (int i = min; i <= max; ++i) |
804 { |
804 { |
805 tokens[i].toDouble (&ok); |
805 tokens[i].toDouble (&ok); |
806 |
806 |
807 if (!ok && !scient.exactMatch (tokens[i])) |
807 if (!ok && !scient.exactMatch (tokens[i])) |
808 throw LDParseError (line, fmt ("Token #%1 was `%2`, expected a number (matched length: %3)", (i + 1), tokens[i], scient.matchedLength())); |
808 throw LDParseError (line, format ("Token #%1 was `%2`, expected a number (matched length: %3)", (i + 1), tokens[i], scient.matchedLength())); |
809 } |
809 } |
810 } |
810 } |
811 |
811 |
812 // ============================================================================= |
812 // ============================================================================= |
813 // |
813 // |
852 |
852 |
853 // Handle BFC statements |
853 // Handle BFC statements |
854 if (tokens.size() > 2 && tokens[1] == "BFC") |
854 if (tokens.size() > 2 && tokens[1] == "BFC") |
855 { |
855 { |
856 for (int i = 0; i < LDBFC::NumStatements; ++i) |
856 for (int i = 0; i < LDBFC::NumStatements; ++i) |
857 if (comm == fmt ("BFC %1", LDBFC::k_statementStrings [i])) |
857 if (comm == format ("BFC %1", LDBFC::k_statementStrings [i])) |
858 return new LDBFC ( (LDBFC::Statement) i); |
858 return new LDBFC ( (LDBFC::Statement) i); |
859 |
859 |
860 // MLCAD is notorious for stuffing these statements in parts it |
860 // MLCAD is notorious for stuffing these statements in parts it |
861 // creates. The above block only handles valid statements, so we |
861 // creates. The above block only handles valid statements, so we |
862 // need to handle MLCAD-style invertnext, clip and noclip separately. |
862 // need to handle MLCAD-style invertnext, clip and noclip separately. |
929 |
929 |
930 // If we cannot open the file, mark it an error. Note we cannot use LDParseError |
930 // If we cannot open the file, mark it an error. Note we cannot use LDParseError |
931 // here because the error object needs the document reference. |
931 // here because the error object needs the document reference. |
932 if (!load) |
932 if (!load) |
933 { |
933 { |
934 LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14])); |
934 LDError* obj = new LDError (line, format ("Could not open %1", tokens[14])); |
935 obj->setFileReferenced (tokens[14]); |
935 obj->setFileReferenced (tokens[14]); |
936 return obj; |
936 return obj; |
937 } |
937 } |
938 |
938 |
939 LDSubfile* obj = new LDSubfile; |
939 LDSubfile* obj = new LDSubfile; |
1045 LDDocument* fileInfo = getDocument (ref->fileInfo()->name()); |
1045 LDDocument* fileInfo = getDocument (ref->fileInfo()->name()); |
1046 |
1046 |
1047 if (fileInfo) |
1047 if (fileInfo) |
1048 ref->setFileInfo (fileInfo); |
1048 ref->setFileInfo (fileInfo); |
1049 else |
1049 else |
1050 ref->replace (new LDError (ref->asText(), fmt ("Could not open %1", ref->fileInfo()->name()))); |
1050 ref->replace (new LDError (ref->asText(), format ("Could not open %1", ref->fileInfo()->name()))); |
1051 } |
1051 } |
1052 |
1052 |
1053 // Reparse gibberish files. It could be that they are invalid because |
1053 // Reparse gibberish files. It could be that they are invalid because |
1054 // of loading errors. Circumstances may be different now. |
1054 // of loading errors. Circumstances may be different now. |
1055 if (obj->type() == LDObject::EError) |
1055 if (obj->type() == LDObject::EError) |
1093 m_objects.insert (pos, obj); |
1093 m_objects.insert (pos, obj); |
1094 obj->setDocument (this); |
1094 obj->setDocument (this); |
1095 |
1095 |
1096 #ifdef DEBUG |
1096 #ifdef DEBUG |
1097 if (!isImplicit()) |
1097 if (!isImplicit()) |
1098 dlog ("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos); |
1098 dprint ("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos); |
1099 #endif |
1099 #endif |
1100 } |
1100 } |
1101 |
1101 |
1102 // ============================================================================= |
1102 // ============================================================================= |
1103 // |
1103 // |
1303 g_win->updateDocumentListItem (f); |
1303 g_win->updateDocumentListItem (f); |
1304 g_win->buildObjList(); |
1304 g_win->buildObjList(); |
1305 g_win->updateTitle(); |
1305 g_win->updateTitle(); |
1306 g_win->R()->setDocument (f); |
1306 g_win->R()->setDocument (f); |
1307 g_win->R()->repaint(); |
1307 g_win->R()->repaint(); |
1308 log ("Changed file to %1", f->getDisplayName()); |
1308 print ("Changed file to %1", f->getDisplayName()); |
1309 } |
1309 } |
1310 } |
1310 } |
1311 |
1311 |
1312 // ============================================================================= |
1312 // ============================================================================= |
1313 // |
1313 // |
1348 delete g_logoedStud2; |
1348 delete g_logoedStud2; |
1349 |
1349 |
1350 g_logoedStud = openDocument ("stud-logo.dat", true); |
1350 g_logoedStud = openDocument ("stud-logo.dat", true); |
1351 g_logoedStud2 = openDocument ("stud2-logo.dat", true); |
1351 g_logoedStud2 = openDocument ("stud2-logo.dat", true); |
1352 |
1352 |
1353 log (LDDocument::tr ("Logoed studs loaded.\n")); |
1353 print (LDDocument::tr ("Logoed studs loaded.\n")); |
1354 } |
1354 } |
1355 |
1355 |
1356 // ============================================================================= |
1356 // ============================================================================= |
1357 // |
1357 // |
1358 void LDDocument::addToSelection (LDObject* obj) // [protected] |
1358 void LDDocument::addToSelection (LDObject* obj) // [protected] |