--- a/src/document.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1186 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 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 <http://www.gnu.org/licenses/>. - * ===================================================================== - * - * file.cpp: File I/O and management. - * - File loading, parsing, manipulation, saving, closing. - * - LDraw path verification. - */ - -#include <QMessageBox> -#include <QFileDialog> -#include <QDir> -#include <QApplication> -#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 "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 = 5; -static bool g_aborted = false; -static LDDocument* g_logoedStud = null; -static LDDocument* g_logoedStud2 = null; - -LDDocument* LDDocument::m_curdoc = null; - -// ============================================================================= -// ----------------------------------------------------------------------------- -namespace LDPaths -{ static str pathError; - - struct - { str LDConfigPath; - str partsPath, primsPath; - } pathInfo; - - void initPaths() - { if (!tryConfigure (io_ldpath)) - { LDrawPathDialog dlg (false); - - if (!dlg.exec()) - exit (0); - - io_ldpath = dlg.filename(); - } - } - - bool tryConfigure (str 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<br />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 - str getError() - { return pathError; - } - - str ldconfig() - { return pathInfo.LDConfigPath; - } - - str prims() - { return pathInfo.primsPath; - } - - str parts() - { return pathInfo.partsPath; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument::LDDocument() -{ setImplicit (true); - setSavePosition (-1); - setListItem (null); - setHistory (new History); - m_History->setFile (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument::~LDDocument() -{ // Clear everything from the model - for (LDObject* obj : getObjects()) - delete obj; - - // Clear the cache as well - for (LDObject* obj : getCache()) - delete obj; - - delete m_History; - - // Remove this file from the list of files - g_loadedFiles.removeOne (this); - - // 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(); - } - - g_win->updateDocumentList(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* findDocument (str name) -{ for (LDDocument * file : g_loadedFiles) - if (!file->getName().isEmpty() && file->getShortName() == name) - return file; - - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str dirname (str 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 ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str basename (str path) -{ long lastpos = path.lastIndexOf (DIRSLASH); - - if (lastpos != -1) - return path.mid (lastpos + 1); - - return path; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File* openLDrawFile (str relpath, bool subdirs) -{ log ("Opening %1...\n", relpath); - File* f = new File; - str fullPath; - - // LDraw models use Windows-style path separators. If we're not on Windows, - // replace the path separator now before opening any files. -#ifndef WIN32 - relpath.replace ("\\", "/"); -#endif // WIN32 - - if (getCurrentDocument()) - { // First, try find the file in the current model's file path. We want a file - // in the immediate vicinity of the current model to override stock LDraw stuff. - str partpath = fmt ("%1" DIRSLASH "%2", dirname (getCurrentDocument()->getName()), relpath); - - if (f->open (partpath, File::Read)) - return f; - } - - if (f->open (relpath, File::Read)) - return f; - - // Try with just the LDraw path first - fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath); - - if (f->open (fullPath, File::Read)) - return f; - - 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 str& topdir : initlist<str> ({ io_ldpath, net_downloadpath })) - { for (const str& subdir : initlist<str> ({ "parts", "p" })) - { fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); - - if (f->open (fullPath, File::Read)) - return f; - } - } - } - - // Did not find the file. - log ("Could not find %1.\n", relpath); - delete f; - 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) - delete obj; - - 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) - { str line = getLines()[i]; - - // Trim the trailing newline - QChar c; - - while (!line.isEmpty() && ((c = line[line.length() - 1]) == '\n' || c == '\r')) - line.chop (1); - - LDObject* obj = parseLine (line); - - // Check for parse errors and warn about tthem - if (obj->getType() == LDObject::Error) - { log ("Couldn't parse line #%1: %2", getProgress() + 1, static_cast<LDError*> (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 + 1); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFileLoader::abort() -{ setAborted (true); - - if (isOnForeground()) - g_aborted = true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok) -{ QList<str> lines; - QList<LDObject*> objs; - - if (numWarnings) - *numWarnings = 0; - - // Calculate the amount of lines - for (str line : *f) - lines << line; - - f->rewind(); - - 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 (str 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.. - File* f; - - if (search) - f = openLDrawFile (path.toLower(), true); - else - { f = new File (path, File::Read); - - if (!*f) - { delete f; - return null; - } - } - - if (!f) - return null; - - LDDocument* load = new LDDocument; - load->setName (path); - - int numWarnings; - bool ok; - QList<LDObject*> objs = loadFileContents (f, &numWarnings, &ok); - - if (!ok) - return null; - - for (LDObject* obj : objs) - load->addObject (obj); - - delete f; - g_loadedFiles << load; - - if (g_loadingMainFile) - { LDDocument::setCurrent (load); - g_win->R()->setFile (load); - log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); - } - - 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()) - { str message = fmt ("There are unsaved changes to %1. Should it be saved?", - (getName().length() > 0) ? getName() : "<anonymous>"); - - int button = msgbox::question (g_win, "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) - { str newpath = QFileDialog::getSaveFileName (g_win, "Save As", - getCurrentDocument()->getName(), "LDraw files (*.dat *.ldr)"); - - if (newpath.length() == 0) - return false; - - setName (newpath); - } - - if (!save()) - { message = fmt (QObject::tr ("Failed to save %1: %2\nDo you still want to close?"), - getName(), strerror (errno)); - - if (msgbox::critical (g_win, "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<LDDocument*> 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 (str path) -{ alias rfiles = io_recentfiles.value; - 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 (str path) -{ g_loadingMainFile = true; - 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); - - // 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 (str savepath) -{ if (!savepath.length()) - savepath = getName(); - - File f (savepath, File::Write); - - if (!f) - 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. If it's saved into a directory - // called "s" or "48", prepend that into the name. - LDComment* fpathComment = null; - LDObject* first = getObject (1); - - if (!isImplicit() && first != null && first->getType() == LDObject::Comment) - { fpathComment = static_cast<LDComment*> (first); - - if (fpathComment->text.left (6) == "Name: ") - { str newname; - str dir = basename (dirname (savepath)); - - if (dir == "s" || dir == "48") - newname = dir + "\\"; - - newname += basename (savepath); - fpathComment->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"); - - // File is saved, now clean up. - f.close(); - - // We have successfully saved, update the save position now. - setSavePosition (getHistory()->getPosition()); - setName (savepath); - - g_win->updateDocumentListItem (this); - g_win->updateTitle(); - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -#define CHECK_TOKEN_COUNT(N) \ - if (tokens.size() != N) \ - return new LDError (line, "Bad amount of tokens"); - -#define CHECK_TOKEN_NUMBERS(MIN, MAX) \ - for (int i = MIN; i <= MAX; ++i) \ - if (!numeric (tokens[i])) \ - return new LDError (line, fmt ("Token #%1 was `%2`, expected a number", (i + 1), tokens[i])); - -// ============================================================================= -// ----------------------------------------------------------------------------- -static vertex parseVertex (QStringList& s, const int n) -{ vertex v; - - for (const Axis ax : g_Axes) - 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 (str line) -{ QStringList tokens = line.split (" ", str::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) - return new LDError (line, "Illogical line code"); - - int num = tokens[0][0].digitValue(); - - switch (num) - { case 0: - { // Comment - str comm = line.mid (line.indexOf ("0") + 1); - - // Remove any leading whitespace - while (comm[0] == ' ') - comm.remove (0, 1); - - // 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 - { const char* a; - LDBFC::Type b; - } BFCData[] = - { { "INVERTNEXT", LDBFC::InvertNext }, - { "NOCLIP", LDBFC::NoClip }, - { "CLIP", LDBFC::Clip } - }; - - for (const auto & i : BFCData) - if (comm == fmt ("BFC CERTIFY %1", 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) - CHECK_TOKEN_COUNT (7) - CHECK_TOKEN_NUMBERS (3, 6) - - LDVertex* obj = new LDVertex; - obj->setColor (tokens[3].toLong()); - - for (const Axis ax : g_Axes) - obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6 - - return obj; - } elif (tokens[2] == "OVERLAY") - - { CHECK_TOKEN_COUNT (9); - CHECK_TOKEN_NUMBERS (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 - CHECK_TOKEN_COUNT (15) - CHECK_TOKEN_NUMBERS (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 - 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: - { CHECK_TOKEN_COUNT (8) - CHECK_TOKEN_NUMBERS (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: - { CHECK_TOKEN_COUNT (11) - CHECK_TOKEN_NUMBERS (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: - { CHECK_TOKEN_COUNT (14) - CHECK_TOKEN_NUMBERS (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 - return new LDError (line, "Unknown line code number"); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* getDocument (str 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::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - LDDocument* fileInfo = getDocument (ref->getFileInfo()->getName()); - - if (fileInfo) - ref->setFileInfo (fileInfo); - else - ref->replace (new LDError (ref->raw(), "Could not open referred file")); - } - - // Reparse gibberish files. It could be that they are invalid because - // of loading errors. Circumstances may be different now. - if (obj->getType() == LDObject::Error) - obj->replace (parseLine (static_cast<LDError*> (obj)->contents)); - } - - // Close all files left unused - LDDocument::closeUnused(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDDocument::addObject (LDObject* obj) -{ getHistory()->add (new AddHistory (getObjects().size(), obj)); - m_Objects << obj; - - if (obj->getType() == LDObject::Vertex) - m_Vertices << obj; - - obj->setFile (this); - return getObjectCount() - 1; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::addObjects (const QList<LDObject*> 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); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::forgetObject (LDObject* obj) -{ int idx = obj->getIndex(); - 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 < m_Objects.size()); - - // Mark this change to history - str oldcode = getObject (idx)->raw(); - str newcode = obj->raw(); - *m_History << new EditHistory (idx, oldcode, newcode); - - obj->setFile (this); - m_Objects[idx] = obj; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static QList<LDDocument*> getFilesUsed (LDDocument* node) -{ QList<LDDocument*> filesUsed; - - for (LDObject* obj : node->getObjects()) - { if (obj->getType() != LDObject::Subfile) - continue; - - LDSubfile* ref = static_cast<LDSubfile*> (obj); - filesUsed << ref->getFileInfo(); - filesUsed << getFilesUsed (ref->getFileInfo()); - } - - return filesUsed; -} - -// ============================================================================= -// Find out which files are unused and close them. -// ----------------------------------------------------------------------------- -void LDDocument::closeUnused() -{ QList<LDDocument*> filesUsed = getFilesUsed (getCurrentDocument()); - - // Anything that's explicitly opened must not be closed - for (LDDocument* file : g_loadedFiles) - if (!file->isImplicit()) - filesUsed << file; - - // Remove duplicated entries - removeDuplicates (filesUsed); - - // Close all open files that aren't in filesUsed - for (LDDocument* file : g_loadedFiles) - { bool isused = false; - - for (LDDocument* usedFile : filesUsed) - { if (file == usedFile) - { isused = true; - break; - } - } - - if (!isused) - delete file; - } - - g_loadedFiles.clear(); - g_loadedFiles << filesUsed; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -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(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDDocument::getShortName() -{ if (!getName().isEmpty()) - return basename (getName()); - - if (!getDefaultName().isEmpty()) - return getDefaultName(); - - return tr ("<anonymous>"); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> 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)) - { if (getName() == "stud.dat" && g_logoedStud) - return g_logoedStud->inlineContents (flags); - - elif (getName() == "stud2.dat" && g_logoedStud2) - return g_logoedStud2->inlineContents (flags); - } - - QList<LDObject*> objs, objcache; - - bool deep = flags & LDSubfile::DeepInline, - doCache = flags & LDSubfile::CacheInline; - - // If we have this cached, just clone that - if (deep && getCache().size()) - { for (LDObject* obj : getCache()) - objs << obj->clone(); - } - 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::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - - // We only want to cache immediate subfiles, so shed the caching - // flag when recursing deeper in hierarchy. - QList<LDObject*> otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline)); - - for (LDObject * otherobj : otherobjs) - { // Cache this object, if desired - if (doCache) - objcache << otherobj->clone(); - - objs << otherobj; - } - } - else - { if (doCache) - objcache << obj->clone(); - - objs << obj->clone(); - } - } - - 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. ;) -// -// FIXME: 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()->resetAllAngles(); - g_win->R()->repaint(); - - log ("Changed file to %1", f->getShortName()); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -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() == "" && - !g_loadedFiles[0]->hasUnsavedChanges() - ) - delete g_loadedFiles[0]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void loadLogoedStuds() -{ log ("Loading logoed studs...\n"); - - delete g_logoedStud; - delete g_logoedStud2; - - g_logoedStud = openDocument ("stud-logo.dat", true); - g_logoedStud2 = openDocument ("stud2-logo.dat", true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -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 QList<LDObject*>& LDDocument::getSelection() const -{ return m_sel; -} \ No newline at end of file