diff -r 6b13e4c2e97b -r b75c6cce02e2 src/document.cc
--- a/src/document.cc Mon Jan 20 23:44:22 2014 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1435 +0,0 @@
-/*
- * LDForge: LDraw parts authoring CAD
- * Copyright (C) 2013, 2014 Santeri Piippo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include
-#include
-#include
-#include "main.h"
-#include "config.h"
-#include "document.h"
-#include "misc.h"
-#include "gui.h"
-#include "history.h"
-#include "dialogs.h"
-#include "gldraw.h"
-#include "misc/invokationDeferer.h"
-#include "moc_document.cpp"
-
-cfg (String, io_ldpath, "");
-cfg (List, io_recentfiles, {});
-extern_cfg (String, net_downloadpath);
-extern_cfg (Bool, gl_logostuds);
-
-static bool g_loadingMainFile = false;
-static const int g_maxRecentFiles = 10;
-static bool g_aborted = false;
-static LDDocumentPointer g_logoedStud = null;
-static LDDocumentPointer g_logoedStud2 = null;
-
-LDDocument* LDDocument::m_curdoc = null;
-
-const QStringList g_specialSubdirectories ({ "s", "48", "8" });
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-namespace LDPaths
-{
- static QString pathError;
-
- struct
- {
- QString LDConfigPath;
- QString partsPath, primsPath;
- } pathInfo;
-
- void initPaths()
- {
- if (!tryConfigure (io_ldpath))
- {
- LDrawPathDialog dlg (false);
-
- if (!dlg.exec())
- exit (0);
-
- io_ldpath = dlg.filename();
- }
- }
-
- bool tryConfigure (QString path)
- {
- QDir dir;
-
- if (!dir.cd (path))
- {
- pathError = "Directory does not exist.";
- return false;
- }
-
- QStringList mustHave = { "LDConfig.ldr", "parts", "p" };
- QStringList contents = dir.entryList (mustHave);
-
- if (contents.size() != mustHave.size())
- {
- pathError = "Not an LDraw directory! Must
have LDConfig.ldr, parts/ and p/.";
- return false;
- }
-
- pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path);
- pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path);
- pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path);
-
- return true;
- }
-
- // Accessors
- QString getError()
- {
- return pathError;
- }
-
- QString ldconfig()
- {
- return pathInfo.LDConfigPath;
- }
-
- QString prims()
- {
- return pathInfo.primsPath;
- }
-
- QString parts()
- {
- return pathInfo.partsPath;
- }
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument::LDDocument() :
- m_gldata (new LDGLData)
-{
- setImplicit (true);
- setSavePosition (-1);
- setListItem (null);
- setHistory (new History);
- m_History->setDocument (this);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument::~LDDocument()
-{
- // Remove this file from the list of files. This MUST be done FIRST, otherwise
- // a ton of other functions will think this file is still valid when it is not!
- g_loadedFiles.removeOne (this);
-
- m_History->setIgnoring (true);
-
- // Clear everything from the model
- for (LDObject* obj : getObjects())
- obj->deleteSelf();
-
- // Clear the cache as well
- for (LDObject* obj : getCache())
- obj->deleteSelf();
-
- delete m_History;
- delete m_gldata;
-
- // If we just closed the current file, we need to set the current
- // file as something else.
- if (this == getCurrentDocument())
- {
- bool found = false;
-
- // Try find an explicitly loaded file - if we can't find one,
- // we need to create a new file to switch to.
- for (LDDocument* file : g_loadedFiles)
- {
- if (!file->isImplicit())
- {
- LDDocument::setCurrent (file);
- found = true;
- break;
- }
- }
-
- if (!found)
- newFile();
- }
-
- if (this == g_logoedStud)
- g_logoedStud = null;
- elif (this == g_logoedStud2)
- g_logoedStud2 = null;
-
- g_win->updateDocumentList();
- log ("Closed %1", getName());
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument* findDocument (QString name)
-{
- for (LDDocument * file : g_loadedFiles)
- if (!file->getName().isEmpty() && file->getName() == name)
- return file;
-
- return null;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QString dirname (QString path)
-{
- long lastpos = path.lastIndexOf (DIRSLASH);
-
- if (lastpos > 0)
- return path.left (lastpos);
-
-#ifndef _WIN32
- if (path[0] == DIRSLASH_CHAR)
- return DIRSLASH;
-#endif // _WIN32
-
- return "";
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QString basename (QString path)
-{
- long lastpos = path.lastIndexOf (DIRSLASH);
-
- if (lastpos != -1)
- return path.mid (lastpos + 1);
-
- return path;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static QString findLDrawFilePath (QString relpath, bool subdirs)
-{
- QString fullPath;
-
- // LDraw models use Windows-style path separators. If we're not on Windows,
- // replace the path separator now before opening any files. Qt expects
- // forward-slashes as directory separators.
-#ifndef WIN32
- relpath.replace ("\\", "/");
-#endif // WIN32
-
- // Try find it relative to other currently open documents. We want a file
- // in the immediate vicinity of a current model to override stock LDraw stuff.
- QString reltop = basename (dirname (relpath));
-
- for (LDDocument* doc : g_loadedFiles)
- {
- if (doc->getFullPath().isEmpty())
- continue;
-
- QString partpath = fmt ("%1/%2", dirname (doc->getFullPath()), relpath);
- QFile f (partpath);
-
- if (f.exists())
- {
- // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48
- QString proptop = basename (dirname (partpath));
-
- bool bogus = false;
-
- for (QString s : g_specialSubdirectories)
- {
- if ((proptop == s && reltop != s) || (reltop == s && proptop != s))
- {
- bogus = true;
- break;
- }
- }
-
- if (!bogus)
- return partpath;
- }
- }
-
- if (QFile::exists (relpath))
- return relpath;
-
- // Try with just the LDraw path first
- fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath);
-
- if (QFile::exists (fullPath))
- return fullPath;
-
- if (subdirs)
- {
- // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's
- // where we download parts from the PT to.
- for (const QString& topdir : initlist ({ io_ldpath, net_downloadpath }))
- {
- for (const QString& subdir : initlist ({ "parts", "p" }))
- {
- fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath);
-
- if (QFile::exists (fullPath))
- return fullPath;
- }
- }
- }
-
- // Did not find the file.
- return "";
-}
-
-QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer)
-{
- log ("Opening %1...\n", relpath);
- QString path = findLDrawFilePath (relpath, subdirs);
-
- if (pathpointer != null)
- *pathpointer = path;
-
- if (path.isEmpty())
- return null;
-
- QFile* fp = new QFile (path);
-
- if (fp->open (QIODevice::ReadOnly))
- return fp;
-
- fp->deleteLater();
- return null;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDFileLoader::start()
-{
- setDone (false);
- setProgress (0);
- setAborted (false);
-
- if (isOnForeground())
- {
- g_aborted = false;
-
- // Show a progress dialog if we're loading the main document.here so we can
- // show progress updates and keep the WM posted that we're still here.
- // Of course we cannot exec() the dialog because then the dialog would
- // block.
- dlg = new OpenProgressDialog (g_win);
- dlg->setNumLines (getLines().size());
- dlg->setModal (true);
- dlg->show();
-
- // Connect the loader in so we can show updates
- connect (this, SIGNAL (workDone()), dlg, SLOT (accept()));
- connect (dlg, SIGNAL (rejected()), this, SLOT (abort()));
- }
- else
- dlg = null;
-
- // Begin working
- work (0);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDFileLoader::work (int i)
-{
- // User wishes to abort, so stop here now.
- if (isAborted())
- {
- for (LDObject* obj : m_Objects)
- obj->deleteSelf();
-
- m_Objects.clear();
- setDone (true);
- return;
- }
-
- // Parse up to 300 lines per iteration
- int max = i + 300;
-
- for (; i < max && i < (int) getLines().size(); ++i)
- {
- QString line = getLines()[i];
-
- // Trim the trailing newline
- QChar c;
-
- while (line.endsWith ("\n") || line.endsWith ("\r"))
- line.chop (1);
-
- LDObject* obj = parseLine (line);
-
- // Check for parse errors and warn about tthem
- if (obj->getType() == LDObject::EError)
- {
- log ("Couldn't parse line #%1: %2", getProgress() + 1, static_cast (obj)->reason);
-
- if (getWarnings() != null)
- (*getWarnings())++;
- }
-
- m_Objects << obj;
- setProgress (i);
-
- // If we have a dialog pointer, update the progress now
- if (isOnForeground())
- dlg->updateProgress (i);
- }
-
- // If we're done now, tell the environment we're done and stop.
- if (i >= ((int) getLines().size()) - 1)
- {
- emit workDone();
- setDone (true);
- return;
- }
-
- // Otherwise, continue, by recursing back.
- if (!isDone())
- {
- // If we have a dialog to show progress output to, we cannot just call
- // work() again immediately as the dialog needs some processor cycles as
- // well. Thus, take a detour through the event loop by using the
- // meta-object system.
- //
- // This terminates the loop here and control goes back to the function
- // which called the file loader. It will keep processing the event loop
- // until we're ready (see loadFileContents), thus the event loop will
- // eventually catch the invokation we throw here and send us back. Though
- // it's not technically recursion anymore, more like a for loop. :P
- if (isOnForeground())
- QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i));
- else
- work (i);
- }
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDFileLoader::abort()
-{
- setAborted (true);
-
- if (isOnForeground())
- g_aborted = true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObjectList loadFileContents (QFile* fp, int* numWarnings, bool* ok)
-{
- QStringList lines;
- LDObjectList objs;
-
- if (numWarnings)
- *numWarnings = 0;
-
- // Read in the lines
- while (fp->atEnd() == false)
- lines << QString::fromUtf8 (fp->readLine());
-
- LDFileLoader* loader = new LDFileLoader;
- loader->setWarnings (numWarnings);
- loader->setLines (lines);
- loader->setOnForeground (g_loadingMainFile);
- loader->start();
-
- // After start() returns, if the loader isn't done yet, it's delaying
- // its next iteration through the event loop. We need to catch this here
- // by telling the event loop to tick, which will tick the file loader again.
- // We keep doing this until the file loader is ready.
- while (loader->isDone() == false)
- qApp->processEvents();
-
- // If we wanted the success value, supply that now
- if (ok)
- *ok = !loader->isAborted();
-
- objs = loader->getObjects();
- return objs;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument* openDocument (QString path, bool search)
-{
- // Convert the file name to lowercase since some parts contain uppercase
- // file names. I'll assume here that the library will always use lowercase
- // file names for the actual parts..
- QFile* fp;
- QString fullpath;
-
- if (search)
- fp = openLDrawFile (path.toLower(), true, &fullpath);
- else
- {
- fp = new QFile (path);
- fullpath = path;
-
- if (!fp->open (QIODevice::ReadOnly))
- {
- delete fp;
- return null;
- }
- }
-
- if (!fp)
- return null;
-
- LDDocument* load = new LDDocument;
- load->setFullPath (fullpath);
- load->setName (LDDocument::shortenName (load->getFullPath()));
- dlog ("name: %1 (%2)", load->getName(), load->getFullPath());
- g_loadedFiles << load;
-
- // Don't take the file loading as actual edits to the file
- load->getHistory()->setIgnoring (true);
-
- int numWarnings;
- bool ok;
- LDObjectList objs = loadFileContents (fp, &numWarnings, &ok);
- fp->close();
- fp->deleteLater();
-
- if (!ok)
- {
- g_loadedFiles.removeOne (load);
- delete load;
- return null;
- }
-
- load->addObjects (objs);
-
- if (g_loadingMainFile)
- {
- LDDocument::setCurrent (load);
- g_win->R()->setFile (load);
- log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings);
- }
-
- load->getHistory()->setIgnoring (false);
- return load;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool LDDocument::isSafeToClose()
-{
- typedef QMessageBox msgbox;
- setlocale (LC_ALL, "C");
-
- // If we have unsaved changes, warn and give the option of saving.
- if (hasUnsavedChanges())
- {
- QString message = fmt (tr ("There are unsaved changes to %1. Should it be saved?"),
- (getName().length() > 0) ? getName() : tr (""));
-
- int button = msgbox::question (g_win, tr ("Unsaved Changes"), message,
- (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel);
-
- switch (button)
- {
- case msgbox::Yes:
- {
- // If we don't have a file path yet, we have to ask the user for one.
- if (getName().length() == 0)
- {
- QString newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"),
- getCurrentDocument()->getName(), tr ("LDraw files (*.dat *.ldr)"));
-
- if (newpath.length() == 0)
- return false;
-
- setName (newpath);
- }
-
- if (!save())
- {
- message = fmt (tr ("Failed to save %1 (%2)\nDo you still want to close?"),
- getName(), strerror (errno));
-
- if (msgbox::critical (g_win, tr ("Save Failure"), message,
- (msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No)
- {
- return false;
- }
- }
- } break;
-
- case msgbox::Cancel:
- return false;
-
- default:
- break;
- }
- }
-
- return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void closeAll()
-{
- // Remove all loaded files and the objects they contain
- QList files = g_loadedFiles;
-
- for (LDDocument* file : files)
- delete file;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void newFile()
-{
- // Create a new anonymous file and set it to our current
- LDDocument* f = new LDDocument;
- f->setName ("");
- f->setImplicit (false);
- g_loadedFiles << f;
- LDDocument::setCurrent (f);
- LDDocument::closeInitialFile();
- g_win->R()->setFile (f);
- g_win->doFullRefresh();
- g_win->updateTitle();
- g_win->updateActions();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void addRecentFile (QString path)
-{
- auto& rfiles = io_recentfiles;
- int idx = rfiles.indexOf (path);
-
- // If this file already is in the list, pop it out.
- if (idx != -1)
- {
- if (rfiles.size() == 1)
- return; // only recent file - abort and do nothing
-
- // Pop it out.
- rfiles.removeAt (idx);
- }
-
- // If there's too many recent files, drop one out.
- while (rfiles.size() > (g_maxRecentFiles - 1))
- rfiles.removeAt (0);
-
- // Add the file
- rfiles << path;
-
- Config::save();
- g_win->updateRecentFilesMenu();
-}
-
-// =============================================================================
-// Open an LDraw file and set it as the main model
-// -----------------------------------------------------------------------------
-void openMainFile (QString path)
-{
- g_loadingMainFile = true;
-
- // If there's already a file with the same name, this file must replace it.
- LDDocument* documentToReplace = null;
- QString shortName = LDDocument::shortenName (path);
-
- for (LDDocument* doc : g_loadedFiles)
- {
- if (doc->getName() == shortName)
- {
- documentToReplace = doc;
- break;
- }
- }
-
- // We cannot open this file if the document this would replace is not
- // safe to close.
- if (documentToReplace != null && documentToReplace->isSafeToClose() == false)
- {
- g_loadingMainFile = false;
- return;
- }
-
- LDDocument* file = openDocument (path, false);
-
- if (!file)
- {
- // Loading failed, thus drop down to a new file since we
- // closed everything prior.
- newFile();
-
- if (!g_aborted)
- {
- // Tell the user loading failed.
- setlocale (LC_ALL, "C");
- critical (fmt (QObject::tr ("Failed to open %1: %2"), path, strerror (errno)));
- }
-
- g_loadingMainFile = false;
- return;
- }
-
- file->setImplicit (false);
-
- // Replace references to the old file with the new file.
- if (documentToReplace != null)
- {
- for (LDDocumentPointer* ptr : documentToReplace->getReferences())
- { dlog ("ptr: %1 (%2)\n",
- ptr, ptr->getPointer() ? ptr->getPointer()->getName() : "");
-
- ptr->operator= (file);
- }
-
- assert (documentToReplace->countReferences() == 0);
- delete documentToReplace;
- }
-
- // If we have an anonymous, unchanged file open as the only open file
- // (aside of the one we just opened), close it now.
- LDDocument::closeInitialFile();
-
- // Rebuild the object tree view now.
- LDDocument::setCurrent (file);
- g_win->doFullRefresh();
-
- // Add it to the recent files list.
- addRecentFile (path);
- g_loadingMainFile = false;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool LDDocument::save (QString savepath)
-{
- if (!savepath.length())
- savepath = getFullPath();
-
- QFile f (savepath);
-
- if (!f.open (QIODevice::WriteOnly))
- return false;
-
- // If the second object in the list holds the file name, update that now.
- // Only do this if the file is explicitly open.
- LDObject* nameObject = getObject (1);
-
- if (!isImplicit() && nameObject != null && nameObject->getType() == LDObject::EComment)
- {
- LDComment* nameComment = static_cast (nameObject);
-
- if (nameComment->text.left (6) == "Name: ")
- {
- QString newname = shortenName (savepath);
- nameComment->text = fmt ("Name: %1", newname);
- g_win->buildObjList();
- }
- }
-
- // File is open, now save the model to it. Note that LDraw requires files to
- // have DOS line endings, so we terminate the lines with \r\n.
- for (LDObject* obj : getObjects())
- f.write ((obj->raw() + "\r\n").toUtf8());
-
- // File is saved, now clean up.
- f.close();
-
- // We have successfully saved, update the save position now.
- setSavePosition (getHistory()->getPosition());
- setFullPath (savepath);
- setName (shortenName (savepath));
-
- g_win->updateDocumentListItem (this);
- g_win->updateTitle();
- return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-class LDParseError : public std::exception
-{
- PROPERTY (private, QString, Error, STR_OPS, STOCK_WRITE)
- PROPERTY (private, QString, Line, STR_OPS, STOCK_WRITE)
-
- public:
- LDParseError (QString line, QString a) : m_Error (a), m_Line (line) {}
-
- const char* what() const throw()
- {
- return getError().toLocal8Bit().constData();
- }
-};
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void checkTokenCount (QString line, const QStringList& tokens, int num)
-{
- if (tokens.size() != num)
- throw LDParseError (line, fmt ("Bad amount of tokens, expected %1, got %2", num, tokens.size()));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void checkTokenNumbers (QString line, const QStringList& tokens, int min, int max)
-{
- bool ok;
-
- // Check scientific notation, e.g. 7.99361e-15
- QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+");
-
- for (int i = min; i <= max; ++i)
- {
- tokens[i].toDouble (&ok);
-
- if (!ok && !scient.exactMatch (tokens[i]))
- throw LDParseError (line, fmt ("Token #%1 was `%2`, expected a number (matched length: %3)", (i + 1), tokens[i], scient.matchedLength()));
- }
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static Vertex parseVertex (QStringList& s, const int n)
-{
- Vertex v;
-
- for_axes (ax)
- v[ax] = s[n + ax].toDouble();
-
- return v;
-}
-
-// =============================================================================
-// This is the LDraw code parser function. It takes in a string containing LDraw
-// code and returns the object parsed from it. parseLine never returns null,
-// the object will be LDError if it could not be parsed properly.
-// -----------------------------------------------------------------------------
-LDObject* parseLine (QString line)
-{
- try
- {
- QStringList tokens = line.split (" ", QString::SkipEmptyParts);
-
- if (tokens.size() <= 0)
- {
- // Line was empty, or only consisted of whitespace
- return new LDEmpty;
- }
-
- if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false)
- throw LDParseError (line, "Illogical line code");
-
- int num = tokens[0][0].digitValue();
-
- switch (num)
- {
- case 0:
- {
- // Comment
- QString comm = line.mid (line.indexOf ("0") + 1).simplified();
-
- // Handle BFC statements
- if (tokens.size() > 2 && tokens[1] == "BFC")
- {
- for (int i = 0; i < LDBFC::NumStatements; ++i)
- if (comm == fmt ("BFC %1", LDBFC::statements [i]))
- return new LDBFC ( (LDBFC::Type) i);
-
- // MLCAD is notorious for stuffing these statements in parts it
- // creates. The above block only handles valid statements, so we
- // need to handle MLCAD-style invertnext, clip and noclip separately.
- struct
- {
- QString a;
- LDBFC::Type b;
- } BFCData[] =
- {
- { "INVERTNEXT", LDBFC::InvertNext },
- { "NOCLIP", LDBFC::NoClip },
- { "CLIP", LDBFC::Clip }
- };
-
- for (const auto& i : BFCData)
- if (comm == "BFC CERTIFY " + i.a)
- return new LDBFC (i.b);
- }
-
- if (tokens.size() > 2 && tokens[1] == "!LDFORGE")
- {
- // Handle LDForge-specific types, they're embedded into comments too
- if (tokens[2] == "VERTEX")
- {
- // Vertex (0 !LDFORGE VERTEX)
- checkTokenCount (line, tokens, 7);
- checkTokenNumbers (line, tokens, 3, 6);
-
- LDVertex* obj = new LDVertex;
- obj->setColor (tokens[3].toLong());
-
- for_axes (ax)
- obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6
-
- return obj;
- } elif (tokens[2] == "OVERLAY")
- {
- checkTokenCount (line, tokens, 9);;
- checkTokenNumbers (line, tokens, 5, 8);
-
- LDOverlay* obj = new LDOverlay;
- obj->setFileName (tokens[3]);
- obj->setCamera (tokens[4].toLong());
- obj->setX (tokens[5].toLong());
- obj->setY (tokens[6].toLong());
- obj->setWidth (tokens[7].toLong());
- obj->setHeight (tokens[8].toLong());
- return obj;
- }
- }
-
- // Just a regular comment:
- LDComment* obj = new LDComment;
- obj->text = comm;
- return obj;
- }
-
- case 1:
- {
- // Subfile
- checkTokenCount (line, tokens, 15);
- checkTokenNumbers (line, tokens, 1, 13);
-
- // Try open the file. Disable g_loadingMainFile temporarily since we're
- // not loading the main file now, but the subfile in question.
- bool tmp = g_loadingMainFile;
- g_loadingMainFile = false;
- LDDocument* load = getDocument (tokens[14]);
- g_loadingMainFile = tmp;
-
- // If we cannot open the file, mark it an error. Note we cannot use LDParseError
- // here because the error object needs the document reference.
- if (!load)
- {
- LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14]));
- obj->setFileReferenced (tokens[14]);
- return obj;
- }
-
- LDSubfile* obj = new LDSubfile;
- obj->setColor (tokens[1].toLong());
- obj->setPosition (parseVertex (tokens, 2)); // 2 - 4
-
- Matrix transform;
-
- for (int i = 0; i < 9; ++i)
- transform[i] = tokens[i + 5].toDouble(); // 5 - 13
-
- obj->setTransform (transform);
- obj->setFileInfo (load);
- return obj;
- }
-
- case 2:
- {
- checkTokenCount (line, tokens, 8);
- checkTokenNumbers (line, tokens, 1, 7);
-
- // Line
- LDLine* obj = new LDLine;
- obj->setColor (tokens[1].toLong());
-
- for (int i = 0; i < 2; ++i)
- obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 7
-
- return obj;
- }
-
- case 3:
- {
- checkTokenCount (line, tokens, 11);
- checkTokenNumbers (line, tokens, 1, 10);
-
- // Triangle
- LDTriangle* obj = new LDTriangle;
- obj->setColor (tokens[1].toLong());
-
- for (int i = 0; i < 3; ++i)
- obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 10
-
- return obj;
- }
-
- case 4:
- case 5:
- {
- checkTokenCount (line, tokens, 14);
- checkTokenNumbers (line, tokens, 1, 13);
-
- // Quadrilateral / Conditional line
- LDObject* obj;
-
- if (num == 4)
- obj = new LDQuad;
- else
- obj = new LDCondLine;
-
- obj->setColor (tokens[1].toLong());
-
- for (int i = 0; i < 4; ++i)
- obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13
-
- return obj;
- }
-
- default: // Strange line we couldn't parse
- throw LDError (line, "Unknown line code number");
- }
- }
- catch (LDParseError& e)
- {
- return new LDError (e.getLine(), e.getError());
- }
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument* getDocument (QString filename)
-{
- // Try find the file in the list of loaded files
- LDDocument* doc = findDocument (filename);
-
- // If it's not loaded, try open it
- if (!doc)
- doc = openDocument (filename, true);
-
- return doc;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void reloadAllSubfiles()
-{
- if (!getCurrentDocument())
- return;
-
- g_loadedFiles.clear();
- g_loadedFiles << getCurrentDocument();
-
- // Go through all objects in the current file and reload the subfiles
- for (LDObject* obj : getCurrentDocument()->getObjects())
- {
- if (obj->getType() == LDObject::ESubfile)
- {
- LDSubfile* ref = static_cast (obj);
- LDDocument* fileInfo = getDocument (ref->getFileInfo()->getName());
-
- if (fileInfo)
- ref->setFileInfo (fileInfo);
- else
- ref->replace (new LDError (ref->raw(), fmt ("Could not open %1", ref->getFileInfo()->getName())));
- }
-
- // Reparse gibberish files. It could be that they are invalid because
- // of loading errors. Circumstances may be different now.
- if (obj->getType() == LDObject::EError)
- obj->replace (parseLine (static_cast (obj)->contents));
- }
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-int LDDocument::addObject (LDObject* obj)
-{
- getHistory()->add (new AddHistory (getObjects().size(), obj));
- m_Objects << obj;
-
- if (obj->getType() == LDObject::EVertex)
- m_Vertices << obj;
-
-#ifdef DEBUG
- if (!isImplicit())
- dlog ("Added object #%1 (%2)\n", obj->getID(), obj->getTypeName());
-#endif
-
- obj->setFile (this);
- return getObjectCount() - 1;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::addObjects (const LDObjectList objs)
-{
- for (LDObject* obj : objs)
- if (obj)
- addObject (obj);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::insertObj (int pos, LDObject* obj)
-{
- getHistory()->add (new AddHistory (pos, obj));
- m_Objects.insert (pos, obj);
- obj->setFile (this);
-
-#ifdef DEBUG
- if (!isImplicit())
- dlog ("Inserted object #%1 (%2) at %3\n", obj->getID(), obj->getTypeName(), pos);
-#endif
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::forgetObject (LDObject* obj)
-{
- int idx = obj->getIndex();
- obj->unselect();
- assert (m_Objects[idx] == obj);
-
- if (!getHistory()->isIgnoring())
- getHistory()->add (new DelHistory (idx, obj));
-
- m_Objects.removeAt (idx);
- obj->setFile (null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool safeToCloseAll()
-{
- for (LDDocument* f : g_loadedFiles)
- if (!f->isSafeToClose())
- return false;
-
- return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::setObject (int idx, LDObject* obj)
-{
- assert (idx >= 0 && idx < m_Objects.size());
-
- // Mark this change to history
- if (!m_History->isIgnoring())
- {
- QString oldcode = getObject (idx)->raw();
- QString newcode = obj->raw();
- *m_History << new EditHistory (idx, oldcode, newcode);
- }
-
- m_Objects[idx]->unselect();
- m_Objects[idx]->setFile (null);
- obj->setFile (this);
- m_Objects[idx] = obj;
-}
-
-// =============================================================================
-// Close all implicit files with no references
-// -----------------------------------------------------------------------------
-void LDDocument::closeUnused()
-{
- for (LDDocument* file : g_loadedFiles)
- if (file->isImplicit() && file->countReferences() == 0)
- delete file;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObject* LDDocument::getObject (int pos) const
-{
- if (m_Objects.size() <= pos)
- return null;
-
- return m_Objects[pos];
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-int LDDocument::getObjectCount() const
-{
- return getObjects().size();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool LDDocument::hasUnsavedChanges() const
-{
- return !isImplicit() && getHistory()->getPosition() != getSavePosition();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QString LDDocument::getDisplayName()
-{
- if (!getName().isEmpty())
- return getName();
-
- if (!getDefaultName().isEmpty())
- return "[" + getDefaultName() + "]";
-
- return tr ("");
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObjectList LDDocument::inlineContents (LDSubfile::InlineFlags flags)
-{
- // Possibly substitute with logoed studs:
- // stud.dat -> stud-logo.dat
- // stud2.dat -> stud-logo2.dat
- if (gl_logostuds && (flags & LDSubfile::RendererInline))
- {
- // Ensure logoed studs are loaded first
- loadLogoedStuds();
-
- if (getName() == "stud.dat" && g_logoedStud)
- return g_logoedStud->inlineContents (flags);
- elif (getName() == "stud2.dat" && g_logoedStud2)
- return g_logoedStud2->inlineContents (flags);
- }
-
- LDObjectList objs, objcache;
-
- bool deep = flags & LDSubfile::DeepInline,
- doCache = flags & LDSubfile::CacheInline;
-
- if (m_needsCache)
- {
- clearCache();
- doCache = true;
- }
-
- // If we have this cached, just create a copy of that
- if (deep && getCache().isEmpty() == false)
- {
- for (LDObject* obj : getCache())
- objs << obj->createCopy();
- }
- else
- {
- if (!deep)
- doCache = false;
-
- for (LDObject* obj : getObjects())
- {
- // Skip those without scemantic meaning
- if (!obj->isScemantic())
- continue;
-
- // Got another sub-file reference, inline it if we're deep-inlining. If not,
- // just add it into the objects normally. Also, we only cache immediate
- // subfiles and this is not one. Yay, recursion!
- if (deep && obj->getType() == LDObject::ESubfile)
- {
- LDSubfile* ref = static_cast (obj);
-
- // We only want to cache immediate subfiles, so shed the caching
- // flag when recursing deeper in hierarchy.
- LDObjectList otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline));
-
- for (LDObject* otherobj : otherobjs)
- {
- // Cache this object, if desired
- if (doCache)
- objcache << otherobj->createCopy();
-
- objs << otherobj;
- }
- }
- else
- {
- if (doCache)
- objcache << obj->createCopy();
-
- objs << obj->createCopy();
- }
- }
-
- if (doCache)
- setCache (objcache);
- }
-
- return objs;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument* LDDocument::current()
-{
- return m_curdoc;
-}
-
-// =============================================================================
-// Sets the given file as the current one on display. At some point in time this
-// was an operation completely unheard of. ;)
-//
-// TODO: f can be temporarily null. This probably should not be the case.
-// -----------------------------------------------------------------------------
-void LDDocument::setCurrent (LDDocument* f)
-{
- // Implicit files were loaded for caching purposes and must never be set
- // current.
- if (f && f->isImplicit())
- return;
-
- m_curdoc = f;
-
- if (g_win && f)
- {
- // A ton of stuff needs to be updated
- g_win->updateDocumentListItem (f);
- g_win->buildObjList();
- g_win->updateTitle();
- g_win->R()->setFile (f);
- g_win->R()->repaint();
- log ("Changed file to %1", f->getDisplayName());
- }
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-int LDDocument::countExplicitFiles()
-{
- int count = 0;
-
- for (LDDocument* f : g_loadedFiles)
- if (f->isImplicit() == false)
- count++;
-
- return count;
-}
-
-// =============================================================================
-// This little beauty closes the initial file that was open at first when opening
-// a new file over it.
-// -----------------------------------------------------------------------------
-void LDDocument::closeInitialFile()
-{
- if (
- countExplicitFiles() == 2 &&
- g_loadedFiles[0]->getName().isEmpty() &&
- g_loadedFiles[1]->getName().isEmpty() == false &&
- !g_loadedFiles[0]->hasUnsavedChanges()
- )
- delete g_loadedFiles[0];
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void loadLogoedStuds()
-{
- if (g_logoedStud && g_logoedStud2)
- return;
-
- delete g_logoedStud;
- delete g_logoedStud2;
-
- g_logoedStud = openDocument ("stud-logo.dat", true);
- g_logoedStud2 = openDocument ("stud2-logo.dat", true);
-
- log (LDDocument::tr ("Logoed studs loaded.\n"));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::addToSelection (LDObject* obj) // [protected]
-{
- if (obj->isSelected())
- return;
-
- assert (obj->getFile() == this);
- m_sel << obj;
- obj->setSelected (true);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::removeFromSelection (LDObject* obj) // [protected]
-{
- if (!obj->isSelected())
- return;
-
- assert (obj->getFile() == this);
- m_sel.removeOne (obj);
- obj->setSelected (false);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::clearSelection()
-{
- for (LDObject* obj : m_sel)
- removeFromSelection (obj);
-
- assert (m_sel.isEmpty());
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-const LDObjectList& LDDocument::getSelection() const
-{
- return m_sel;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::swapObjects (LDObject* one, LDObject* other)
-{
- int a = m_Objects.indexOf (one);
- int b = m_Objects.indexOf (other);
- assert (a != b && a != -1 && b != -1);
- m_Objects[b] = one;
- m_Objects[a] = other;
- addToHistory (new SwapHistory (one->getID(), other->getID()));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QString LDDocument::shortenName (QString a) // [static]
-{
- QString shortname = basename (a);
- QString topdirname = basename (dirname (a));
-
- if (g_specialSubdirectories.contains (topdirname))
- shortname.prepend (topdirname + "\\");
-
- return shortname;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::addReference (LDDocumentPointer* ptr)
-{
- pushToReferences (ptr);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::removeReference (LDDocumentPointer* ptr)
-{
- removeFromReferences (ptr);
-
- if (getReferences().size() == 0)
- invokeLater (closeUnused);
-}
\ No newline at end of file