src/ldDocument.cpp

changeset 997
1b49f34e533d
parent 994
09e1a3e272ec
child 998
5be0ce31ce60
equal deleted inserted replaced
995:7986584e7498 997:1b49f34e533d
34 #include "dialogs/openprogressdialog.h" 34 #include "dialogs/openprogressdialog.h"
35 35
36 ConfigOption (QStringList RecentFiles) 36 ConfigOption (QStringList RecentFiles)
37 ConfigOption (bool TryDownloadMissingFiles = false) 37 ConfigOption (bool TryDownloadMissingFiles = false)
38 38
39 static bool g_loadingMainFile = false; 39 LDDocument::LDDocument (DocumentManager* parent) :
40 enum { MAX_RECENT_FILES = 10 };
41 static LDDocument* g_logoedStud;
42 static LDDocument* g_logoedStud2;
43 static bool g_loadingLogoedStuds = false;
44 const QStringList g_specialSubdirectories ({ "s", "48", "8" });
45
46 LDDocument::LDDocument (QObject* parent) :
47 QObject (parent), 40 QObject (parent),
48 HierarchyElement (parent), 41 HierarchyElement (parent),
49 m_history (new EditHistory (this)), 42 m_history (new EditHistory (this)),
50 m_isCache (true), 43 m_isCache (true),
51 m_verticesOutdated (true), 44 m_verticesOutdated (true),
52 m_needVertexMerge (true), 45 m_needVertexMerge (true),
53 m_beingDestroyed (false), 46 m_beingDestroyed (false),
54 m_gldata (new LDGLData) 47 m_gldata (new LDGLData),
48 m_manager (parent)
55 { 49 {
56 setSavePosition (-1); 50 setSavePosition (-1);
57 setTabIndex (-1); 51 setTabIndex (-1);
58 m_needsReCache = true; 52 m_needsReCache = true;
59 } 53 }
60 54
61 LDDocument::~LDDocument() 55 LDDocument::~LDDocument()
62 { 56 {
57 for (int i = 0; i < m_objects.size(); ++i)
58 delete m_objects[i];
59
63 m_beingDestroyed = true; 60 m_beingDestroyed = true;
64 delete m_history; 61 delete m_history;
65 delete m_gldata; 62 delete m_gldata;
66 } 63 }
67 64
189 } 186 }
190 187
191 LDGLData* LDDocument::glData() 188 LDGLData* LDDocument::glData()
192 { 189 {
193 return m_gldata; 190 return m_gldata;
194 }
195
196 // =============================================================================
197 //
198 LDDocument* FindDocument (QString name)
199 {
200 for (LDDocument* document : g_win->allDocuments())
201 {
202 if (isOneOf (name, document->name(), document->defaultName()))
203 return document;
204 }
205
206 return nullptr;
207 }
208
209 // =============================================================================
210 //
211 QString Dirname (QString path)
212 {
213 long lastpos = path.lastIndexOf (DIRSLASH);
214
215 if (lastpos > 0)
216 return path.left (lastpos);
217
218 #ifndef _WIN32
219 if (path[0] == DIRSLASH_CHAR)
220 return DIRSLASH;
221 #endif // _WIN32
222
223 return "";
224 }
225
226 // =============================================================================
227 //
228 QString Basename (QString path)
229 {
230 long lastpos = path.lastIndexOf (DIRSLASH);
231
232 if (lastpos != -1)
233 return path.mid (lastpos + 1);
234
235 return path;
236 }
237
238 // =============================================================================
239 //
240 static QString FindDocumentPath (QString relpath, bool subdirs)
241 {
242 QString fullPath;
243
244 // LDraw models use backslashes as path separators. Replace those into forward slashes for Qt.
245 relpath.replace ("\\", "/");
246
247 // Try find it relative to other currently open documents. We want a file in the immediate vicinity of a current
248 // part model to override stock LDraw stuff.
249 QString reltop = Basename (Dirname (relpath));
250
251 for (LDDocument* doc : g_win->allDocuments())
252 {
253 QString partpath = format ("%1/%2", Dirname (doc->fullPath()), relpath);
254 QFile f (partpath);
255
256 if (f.exists())
257 {
258 // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48
259 QString proptop = Basename (Dirname (partpath));
260
261 bool bogus = false;
262
263 for (QString s : g_specialSubdirectories)
264 {
265 if ((proptop == s and reltop != s) or (reltop == s and proptop != s))
266 {
267 bogus = true;
268 break;
269 }
270 }
271
272 if (not bogus)
273 return partpath;
274 }
275 }
276
277 if (QFile::exists (relpath))
278 return relpath;
279
280 // Try with just the LDraw path first
281 fullPath = format ("%1" DIRSLASH "%2", g_win->configBag()->lDrawPath(), relpath);
282
283 if (QFile::exists (fullPath))
284 return fullPath;
285
286 if (subdirs)
287 {
288 // Look in sub-directories: parts and p. Also look in the download path, since that's where we download parts
289 // from the PT to.
290 QStringList dirs = { g_win->configBag()->lDrawPath(), g_win->configBag()->downloadFilePath() };
291 for (const QString& topdir : dirs)
292 {
293 for (const QString& subdir : QStringList ({ "parts", "p" }))
294 {
295 fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath);
296
297 if (QFile::exists (fullPath))
298 return fullPath;
299 }
300 }
301 }
302
303 // Did not find the file.
304 return "";
305 }
306
307 // =============================================================================
308 //
309 QFile* OpenLDrawFile (QString relpath, bool subdirs, QString* pathpointer)
310 {
311 print ("Opening %1...\n", relpath);
312 QString path = FindDocumentPath (relpath, subdirs);
313
314 if (pathpointer)
315 *pathpointer = path;
316
317 if (path.isEmpty())
318 return nullptr;
319
320 QFile* fp = new QFile (path);
321
322 if (fp->open (QIODevice::ReadOnly))
323 return fp;
324
325 fp->deleteLater();
326 return nullptr;
327 }
328
329 // =============================================================================
330 //
331 LDObjectList LoadFileContents (QFile* fp, int* numWarnings, bool* ok)
332 {
333 LDObjectList objs;
334
335 if (numWarnings)
336 *numWarnings = 0;
337
338 DocumentLoader* loader = new DocumentLoader (g_loadingMainFile);
339 loader->read (fp);
340 loader->start();
341
342 // After start() returns, if the loader isn't done yet, it's delaying
343 // its next iteration through the event loop. We need to catch this here
344 // by telling the event loop to tick, which will tick the file loader again.
345 // We keep doing this until the file loader is ready.
346 while (not loader->isDone())
347 qApp->processEvents();
348
349 // If we wanted the success value, supply that now
350 if (ok)
351 *ok = not loader->hasAborted();
352
353 objs = loader->objects();
354 delete loader;
355 return objs;
356 }
357
358 // =============================================================================
359 //
360 LDDocument* OpenDocument (QString path, bool search, bool implicit, LDDocument* fileToOverride, bool* aborted)
361 {
362 // Convert the file name to lowercase when searching because some parts contain subfile
363 // subfile references with uppercase file names. I'll assume here that the library will always
364 // use lowercase file names for the part files.
365 QFile* fp;
366 QString fullpath;
367
368 if (search)
369 {
370 fp = OpenLDrawFile (path.toLower(), true, &fullpath);
371 }
372 else
373 {
374 fp = new QFile (path);
375 fullpath = path;
376
377 if (not fp->open (QIODevice::ReadOnly))
378 {
379 delete fp;
380 return nullptr;
381 }
382 }
383
384 if (not fp)
385 return nullptr;
386
387 LDDocument* load = (fileToOverride ? fileToOverride : g_win->newDocument (implicit));
388 load->setFullPath (fullpath);
389 load->setName (LDDocument::shortenName (load->fullPath()));
390
391 // Loading the file shouldn't count as actual edits to the document.
392 load->history()->setIgnoring (true);
393
394 int numWarnings;
395 bool ok;
396 LDObjectList objs = LoadFileContents (fp, &numWarnings, &ok);
397 fp->close();
398 fp->deleteLater();
399
400 if (aborted)
401 *aborted = ok == false;
402
403 if (not ok)
404 {
405 load->close();
406 return nullptr;
407 }
408
409 load->addObjects (objs);
410
411 if (g_loadingMainFile)
412 {
413 g_win->changeDocument (load);
414 g_win->renderer()->setDocument (load);
415 print (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings);
416 }
417
418 load->history()->setIgnoring (false);
419 return load;
420 } 191 }
421 192
422 // ============================================================================= 193 // =============================================================================
423 // 194 //
424 // Performs safety checks. Do this before closing any files! 195 // Performs safety checks. Do this before closing any files!
473 break; 244 break;
474 } 245 }
475 } 246 }
476 247
477 return true; 248 return true;
478 }
479
480 // =============================================================================
481 //
482 void CloseAllDocuments()
483 {
484 for (LDDocument* file : g_win->allDocuments())
485 file->close();
486 }
487
488 // =============================================================================
489 //
490 void AddRecentFile (QString path)
491 {
492 QStringList recentFiles = g_win->configBag()->recentFiles();
493 int idx = recentFiles.indexOf (path);
494
495 // If this file already is in the list, pop it out.
496 if (idx != -1)
497 {
498 if (idx == recentFiles.size() - 1)
499 return; // first recent file - abort and do nothing
500
501 recentFiles.removeAt (idx);
502 }
503
504 // If there's too many recent files, drop one out.
505 while (recentFiles.size() > (MAX_RECENT_FILES - 1))
506 recentFiles.removeAt (0);
507
508 // Add the file
509 recentFiles << path;
510 g_win->configBag()->setRecentFiles (recentFiles);
511 g_win->syncSettings();
512 g_win->updateRecentFilesMenu();
513 }
514
515 // =============================================================================
516 // Open an LDraw file and set it as the main model
517 // =============================================================================
518 void OpenMainModel (QString path)
519 {
520 // If there's already a file with the same name, this file must replace it.
521 LDDocument* documentToReplace = nullptr;
522 LDDocument* file = nullptr;
523 QString shortName = LDDocument::shortenName (path);
524
525 for (LDDocument* doc : g_win->allDocuments())
526 {
527 if (doc->name() == shortName)
528 {
529 documentToReplace = doc;
530 break;
531 }
532 }
533
534 // We cannot open this file if the document this would replace is not
535 // safe to close.
536 if (documentToReplace and not documentToReplace->isSafeToClose())
537 return;
538
539 g_loadingMainFile = true;
540
541 // If we're replacing an existing document, clear the document and
542 // make it ready for being loaded to.
543 if (documentToReplace)
544 {
545 file = documentToReplace;
546 file->clear();
547 }
548
549 bool aborted;
550 file = OpenDocument (path, false, false, file, &aborted);
551
552 if (file == nullptr)
553 {
554 if (not aborted)
555 {
556 // Tell the user loading failed.
557 setlocale (LC_ALL, "C");
558 Critical (format (QObject::tr ("Failed to open %1: %2"), path, strerror (errno)));
559 }
560
561 g_loadingMainFile = false;
562 return;
563 }
564
565 file->openForEditing();
566 g_win->closeInitialDocument();
567 g_win->changeDocument (file);
568 g_win->doFullRefresh();
569 AddRecentFile (path);
570 g_loadingMainFile = false;
571
572 // If there were problems loading subfile references, try see if we can find these
573 // files on the parts tracker.
574 QStringList unknowns;
575
576 for (LDObject* obj : file->objects())
577 {
578 if (obj->type() != OBJ_Error or static_cast<LDError*> (obj)->fileReferenced().isEmpty())
579 continue;
580
581 unknowns << static_cast<LDError*> (obj)->fileReferenced();
582 }
583
584 if (g_win->configBag()->tryDownloadMissingFiles() and not unknowns.isEmpty())
585 {
586 PartDownloader dl (g_win);
587 dl.setSourceType (PartDownloader::PartsTracker);
588 dl.setPrimaryFile (file);
589
590 for (QString const& unknown : unknowns)
591 dl.downloadFromPartsTracker (unknown);
592
593 dl.exec();
594 dl.checkIfFinished();
595 file->reloadAllSubfiles();
596 }
597 } 249 }
598 250
599 // ============================================================================= 251 // =============================================================================
600 // 252 //
601 bool LDDocument::save (QString path, int64* sizeptr) 253 bool LDDocument::save (QString path, int64* sizeptr)
898 } 550 }
899 } 551 }
900 552
901 // ============================================================================= 553 // =============================================================================
902 // 554 //
903 LDDocument* GetDocument (QString filename)
904 {
905 // Try find the file in the list of loaded files
906 LDDocument* doc = FindDocument (filename);
907
908 // If it's not loaded, try open it
909 if (not doc)
910 doc = OpenDocument (filename, true, true);
911
912 return doc;
913 }
914
915 // =============================================================================
916 //
917 void LDDocument::reloadAllSubfiles() 555 void LDDocument::reloadAllSubfiles()
918 { 556 {
919 print ("Reloading subfiles of %1", getDisplayName()); 557 print ("Reloading subfiles of %1", getDisplayName());
920 558
921 // Go through all objects in the current file and reload the subfiles 559 // Go through all objects in the current file and reload the subfiles
1024 } 662 }
1025 } 663 }
1026 664
1027 // ============================================================================= 665 // =============================================================================
1028 // 666 //
1029 bool IsSafeToCloseAll()
1030 {
1031 for (LDDocument* f : g_win->allDocuments())
1032 {
1033 if (not f->isSafeToClose())
1034 return false;
1035 }
1036
1037 return true;
1038 }
1039
1040 // =============================================================================
1041 //
1042 void LDDocument::setObject (int idx, LDObject* obj) 667 void LDDocument::setObject (int idx, LDObject* obj)
1043 { 668 {
1044 if (idx < 0 or idx >= m_objects.size()) 669 if (idx < 0 or idx >= m_objects.size())
1045 return; 670 return;
1046 671
1167 792
1168 // ============================================================================= 793 // =============================================================================
1169 // ----------------------------------------------------------------------------- 794 // -----------------------------------------------------------------------------
1170 LDObjectList LDDocument::inlineContents (bool deep, bool renderinline) 795 LDObjectList LDDocument::inlineContents (bool deep, bool renderinline)
1171 { 796 {
1172 // Possibly substitute with logoed studs:
1173 // stud.dat -> stud-logo.dat
1174 // stud2.dat -> stud-logo2.dat
1175 if (m_config->useLogoStuds() and renderinline)
1176 {
1177 // Ensure logoed studs are loaded first
1178 LoadLogoStuds();
1179
1180 if (name() == "stud.dat" and g_logoedStud)
1181 return g_logoedStud->inlineContents (deep, renderinline);
1182 else if (name() == "stud2.dat" and g_logoedStud2)
1183 return g_logoedStud2->inlineContents (deep, renderinline);
1184 }
1185
1186 LDObjectList objs, objcache; 797 LDObjectList objs, objcache;
798
799 if (m_manager->preInline (this, objs))
800 return objs; // Manager dealt with this inline
1187 801
1188 for (LDObject* obj : objects()) 802 for (LDObject* obj : objects())
1189 { 803 {
1190 // Skip those without scemantic meaning 804 // Skip those without scemantic meaning
1191 if (not obj->isScemantic()) 805 if (not obj->isScemantic())
1205 return objs; 819 return objs;
1206 } 820 }
1207 821
1208 // ============================================================================= 822 // =============================================================================
1209 // 823 //
1210 void LoadLogoStuds()
1211 {
1212 if (g_loadingLogoedStuds or (g_logoedStud and g_logoedStud2))
1213 return;
1214
1215 g_loadingLogoedStuds = true;
1216 g_logoedStud = OpenDocument ("stud-logo.dat", true, true);
1217 g_logoedStud2 = OpenDocument ("stud2-logo.dat", true, true);
1218 print (QObject::tr ("Logoed studs loaded.\n"));
1219 g_loadingLogoedStuds = false;
1220 }
1221
1222 // =============================================================================
1223 //
1224 void LDDocument::addToSelection (LDObject* obj) // [protected] 824 void LDDocument::addToSelection (LDObject* obj) // [protected]
1225 { 825 {
1226 if (obj->isSelected() and obj->document() == this) 826 if (obj->isSelected() and obj->document() == this)
1227 { 827 {
1228 m_sel << obj; 828 m_sel << obj;

mercurial