Thu, 12 Dec 2013 19:44:09 +0200
- LDFile renamed to LDDocument, file.h -> document.h
- Added the INSTALL document
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/INSTALL Thu Dec 12 19:44:09 2013 +0200 @@ -0,0 +1,16 @@ +You need Qt4 or Qt5 with OpenGL and networking support. On Debian and +derivatives, e.g. Ubuntu, you'll need: + sudo apt-get install build-essential libqt4-dev libqt4-opengl-dev + libqt4-network + +You may also replace qt4 with qt5 in the above for Qt5. + +To compile: +- enter the root directory +- enter 'qmake' +- enter 'make release' or 'make debug'. A release build is made by default. + +Troubleshooting: +- src/gldraw.h:22:21: fatal error: QGLWidget: No such file or directory +You are missing the OpenGL development package, on Debian and derivatives, +this is libqt4-opengl-dev or libqt5-opengl-dev. \ No newline at end of file
--- a/src/addObjectDialog.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/addObjectDialog.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -27,7 +27,7 @@ #include <QPushButton> #include "gui.h" #include "addObjectDialog.h" -#include "file.h" +#include "document.h" #include "colors.h" #include "colorSelectDialog.h" #include "history.h" @@ -383,7 +383,7 @@ if (name.length() == 0) return; // no subfile filename - LDFile* file = getFile (name); + LDDocument* file = getDocument (name); if (!file) { critical (fmt ("Couldn't open `%1': %2", name, strerror (errno))); @@ -409,7 +409,7 @@ if (newObject) { int idx = g_win->getInsertionPoint(); - LDFile::current()->insertObj (idx, obj); + getCurrentDocument()->insertObj (idx, obj); } g_win->doFullRefresh();
--- a/src/colors.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/colors.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -23,7 +23,7 @@ #include "main.h" #include "colors.h" -#include "file.h" +#include "document.h" #include "misc.h" #include "gui.h" #include "ldconfig.h"
--- a/src/config.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/config.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -30,7 +30,7 @@ #include "config.h" #include "misc.h" #include "gui.h" -#include "file.h" +#include "document.h" #ifdef _WIN32 # define EXTENSION ".ini"
--- a/src/configDialog.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/configDialog.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -31,7 +31,7 @@ #include <QCheckBox> #include "main.h" #include "configDialog.h" -#include "file.h" +#include "document.h" #include "config.h" #include "misc.h" #include "colors.h" @@ -338,7 +338,7 @@ g_win->R()->setBackground(); g_win->doFullRefresh(); g_win->updateToolBars(); - g_win->updateFileList(); + g_win->updateDocumentList(); } // =============================================================================
--- a/src/dialogs.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/dialogs.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -35,7 +35,7 @@ #include "gui.h" #include "gldraw.h" #include "docs.h" -#include "file.h" +#include "document.h" #include "dialogs.h" #include "ui_overlay.h" #include "ui_ldrawpath.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/document.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -0,0 +1,1186 @@ +/* + * 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/document.h Thu Dec 12 19:44:09 2013 +0200 @@ -0,0 +1,212 @@ +/* + * 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/>. + */ + +#ifndef LDFORGE_DOCUMENT_H +#define LDFORGE_DOCUMENT_H + +#include "main.h" +#include "ldtypes.h" +#include "history.h" +#include <QObject> + +class History; +class OpenProgressDialog; + +namespace LDPaths +{ void initPaths(); + bool tryConfigure (str path); + + str ldconfig(); + str prims(); + str parts(); + str getError(); +} + +// ============================================================================= +// LDDocument +// +// The LDDocument class stores a document opened in LDForge either as a editable +// file for the user or for subfile caching. Its methods handle file input and +// output. +// +// A file is implicit when they are opened automatically for caching purposes +// and are hidden from the user. User-opened files are explicit (not implicit). +// +// The default name is a placeholder, initially suggested name for a file. The +// primitive generator uses this to give initial names to primitives. +// ============================================================================= +class LDDocument : public QObject +{ properties: + Q_OBJECT + PROPERTY (private, QList<LDObject*>, Objects, NO_OPS, STOCK_WRITE) + PROPERTY (private, History*, History, NO_OPS, STOCK_WRITE) + PROPERTY (private, QList<LDObject*>, Vertices, NO_OPS, STOCK_WRITE) + PROPERTY (public, str, Name, STR_OPS, STOCK_WRITE) + PROPERTY (public, str, DefaultName, STR_OPS, STOCK_WRITE) + PROPERTY (public, bool, Implicit, BOOL_OPS, STOCK_WRITE) + PROPERTY (public, QList<LDObject*>, Cache, NO_OPS, STOCK_WRITE) + PROPERTY (public, long, SavePosition, NUM_OPS, STOCK_WRITE) + PROPERTY (public, QListWidgetItem*, ListItem, NO_OPS, STOCK_WRITE) + + public: + LDDocument(); + ~LDDocument(); + + int addObject (LDObject* obj); // Adds an object to this file at the end of the file. + void addObjects (const QList<LDObject*> objs); + void clearSelection(); + void forgetObject (LDObject* obj); // Deletes the given object from the object chain. + str getShortName(); + const QList<LDObject*>& getSelection() const; + bool hasUnsavedChanges() const; // Does this document.have unsaved changes? + QList<LDObject*> inlineContents (LDSubfile::InlineFlags flags); + void insertObj (int pos, LDObject* obj); + int getObjectCount() const; + LDObject* getObject (int pos) const; + bool save (str path = ""); // Saves this file to disk. + bool isSafeToClose(); // Perform safety checks. Do this before closing any files! + void setObject (int idx, LDObject* obj); + + inline LDDocument& operator<< (LDObject* obj) + { addObject (obj); + return *this; + } + + inline void addHistoryStep() + { m_History->addStep(); + } + + inline void undo() + { m_History->undo(); + } + + inline void redo() + { m_History->redo(); + } + + inline void clearHistory() + { m_History->clear(); + } + + inline void addToHistory (AbstractHistoryEntry* entry) + { *m_History << entry; + } + + static void closeUnused(); + static LDDocument* current(); + static void setCurrent (LDDocument* f); + static void closeInitialFile(); + static int countExplicitFiles(); + + protected: + void addToSelection (LDObject* obj); + void removeFromSelection (LDObject* obj); + friend class LDObject; + + private: + QList<LDObject*> m_sel; + + static LDDocument* m_curdoc; +}; + +inline LDDocument* getCurrentDocument() +{ return LDDocument::current(); +} + +// Close all current loaded files and start off blank. +void newFile(); + +// Opens the given file as the main file. Everything is closed first. +void openMainFile (str path); + +// Finds an OpenFile by name or null if not open +LDDocument* findDocument (str name); + +// Opens the given file and parses the LDraw code within. Returns a pointer +// to the opened file or null on error. +LDDocument* openDocument (str path, bool search); + +// Opens the given file and returns a pointer to it, potentially looking in /parts and /p +File* openLDrawFile (str relpath, bool subdirs); + +// Close all open files, whether user-opened or subfile caches. +void closeAll(); + +// Parses a string line containing an LDraw object and returns the object parsed. +LDObject* parseLine (str line); + +// Retrieves the pointer to the given document by file name. Document is loaded +// from file if necessary. Can return null if neither succeeds. +LDDocument* getDocument (str filename); + +// Re-caches all subfiles. +void reloadAllSubfiles(); + +// Is it safe to close all files? +bool safeToCloseAll(); + +QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok = null); + +extern QList<LDDocument*> g_loadedFiles; + +inline const QList<LDObject*>& selection() +{ return getCurrentDocument()->getSelection(); +} + +void addRecentFile (str path); +void loadLogoedStuds(); +str basename (str path); +str dirname (str path); + +extern QList<LDDocument*> g_loadedFiles; // Vector of all currently opened files. + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// FileLoader +// +// Loads the given file and parses it to LDObjects using parseLine. It's a +// separate class so as to be able to do the work progressively through the +// event loop, allowing the program to maintain responsivity during loading. +// ============================================================================= +class LDFileLoader : public QObject +{ Q_OBJECT + PROPERTY (private, QList<LDObject*>, Objects, NO_OPS, STOCK_WRITE) + PROPERTY (private, bool, Done, BOOL_OPS, STOCK_WRITE) + PROPERTY (private, int, Progress, NUM_OPS, STOCK_WRITE) + PROPERTY (private, bool, Aborted, BOOL_OPS, STOCK_WRITE) + PROPERTY (public, QStringList, Lines, NO_OPS, STOCK_WRITE) + PROPERTY (public, int*, Warnings, NO_OPS, STOCK_WRITE) + PROPERTY (public, bool, OnForeground, BOOL_OPS, STOCK_WRITE) + + public slots: + void start(); + void abort(); + + private: + OpenProgressDialog* dlg; + + private slots: + void work (int i); + + signals: + void progressUpdate (int progress); + void workDone(); +}; + +#endif // LDFORGE_DOCUMENT_H
--- a/src/download.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/download.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -26,7 +26,7 @@ #include "ui_downloadfrom.h" #include "types.h" #include "gui.h" -#include "file.h" +#include "document.h" #include "gldraw.h" #include "configDialog.h" #include "moc_download.cpp" @@ -263,7 +263,7 @@ // Update everything now if (getPrimaryFile()) - { LDFile::setCurrent (getPrimaryFile()); + { LDDocument::setCurrent (getPrimaryFile()); reloadAllSubfiles(); g_win->doFullRefresh(); g_win->R()->resetAngles(); @@ -412,7 +412,7 @@ } // Try to load this file now. - LDFile* f = openDATFile (getFilePath(), false); + LDDocument* f = openDocument (getFilePath(), false); if (!f) return;
--- a/src/download.h Sat Dec 07 01:18:21 2013 +0200 +++ b/src/download.h Thu Dec 12 19:44:09 2013 +0200 @@ -23,7 +23,7 @@ #include "main.h" #include "types.h" -class LDFile; +class LDDocument; class QFile; class PartDownloadRequest; class Ui_DownloadFrom; @@ -56,7 +56,7 @@ properties: Q_OBJECT - PROPERTY (public, LDFile*, PrimaryFile, NO_OPS, STOCK_WRITE) + PROPERTY (public, LDDocument*, PrimaryFile, NO_OPS, STOCK_WRITE) PROPERTY (public, bool, Aborted, BOOL_OPS, STOCK_WRITE) PROPERTY (private, Ui_DownloadFrom*, Interface, NO_OPS, STOCK_WRITE) PROPERTY (private, QStringList, FilesToDownload, LIST_OPS, STOCK_WRITE)
--- a/src/extprogs.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/extprogs.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -28,7 +28,7 @@ #include "config.h" #include "misc.h" #include "gui.h" -#include "file.h" +#include "document.h" #include "widgets.h" #include "history.h" #include "ui_ytruder.h" @@ -190,7 +190,7 @@ void writeColorGroup (const int colnum, str fname) { QList<LDObject*> objects; - for (LDObject* obj : LDFile::current()->getObjects()) + for (LDObject* obj : getCurrentDocument()->getObjects()) { if (obj->isColored() == false || obj->getColor() != colnum) continue; @@ -278,7 +278,7 @@ g_win->deleteByColor (colnum); // Insert the new objects - LDFile::current()->clearSelection(); + getCurrentDocument()->clearSelection(); for (LDObject * obj : objs) { if (!obj->isScemantic()) @@ -286,7 +286,7 @@ continue; } - LDFile::current()->addObject (obj); + getCurrentDocument()->addObject (obj); obj->select(); }
--- a/src/file.cpp Sat Dec 07 01:18:21 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 "file.h" -#include "misc.h" -#include "gui.h" -#include "history.h" -#include "dialogs.h" -#include "gldraw.h" -#include "moc_file.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 LDFile* g_logoedStud = null; -static LDFile* g_logoedStud2 = null; - -LDFile* LDFile::m_curfile = 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; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile::LDFile() -{ setImplicit (true); - setSavePosition (-1); - setListItem (null); - setHistory (new History); - m_History->setFile (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile::~LDFile() -{ // 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 == LDFile::current()) - { 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 (LDFile* file : g_loadedFiles) - { if (!file->isImplicit()) - { LDFile::setCurrent (file); - found = true; - break; - } - } - - if (!found) - newFile(); - } - - g_win->updateFileList(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile* findLoadedFile (str name) -{ for (LDFile * 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 (LDFile::current()) - { // 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 (LDFile::current()->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 FileLoader::start() -{ setDone (false); - setProgress (0); - setAborted (false); - - if (isOnForeground()) - { g_aborted = false; - - // Show a progress dialog if we're loading the main file 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 FileLoader::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 FileLoader::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(); - - FileLoader* loader = new FileLoader; - 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; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile* openDATFile (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; - - LDFile* load = new LDFile; - 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) - { LDFile::setCurrent (load); - g_win->R()->setFile (load); - log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); - } - - return load; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDFile::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", - LDFile::current()->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<LDFile*> files = g_loadedFiles; - -for (LDFile * file : files) - delete file; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void newFile() -{ // Create a new anonymous file and set it to our current - LDFile* f = new LDFile; - f->setName (""); - f->setImplicit (false); - g_loadedFiles << f; - LDFile::setCurrent (f); - LDFile::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; - LDFile* file = openDATFile (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. - LDFile::closeInitialFile(); - - // Rebuild the object tree view now. - LDFile::setCurrent (file); - g_win->doFullRefresh(); - - // Add it to the recent files list. - addRecentFile (path); - g_loadingMainFile = false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDFile::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->updateFileListItem (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; - LDFile* load = getFile (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"); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile* getFile (str filename) -{ // Try find the file in the list of loaded files - LDFile* load = findLoadedFile (filename); - - // If it's not loaded, try open it - if (!load) - load = openDATFile (filename, true); - - return load; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void reloadAllSubfiles() -{ if (!LDFile::current()) - return; - - g_loadedFiles.clear(); - g_loadedFiles << LDFile::current(); - - // Go through all objects in the current file and reload the subfiles - for (LDObject* obj : LDFile::current()->getObjects()) - { if (obj->getType() == LDObject::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - LDFile* fileInfo = getFile (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 - LDFile::closeUnused(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDFile::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 LDFile::addObjects (const QList<LDObject*> objs) -{ for (LDObject * obj : objs) - if (obj) - addObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::insertObj (int pos, LDObject* obj) -{ getHistory()->add (new AddHistory (pos, obj)); - m_Objects.insert (pos, obj); - obj->setFile (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::forgetObject (LDObject* obj) -{ int idx = obj->getIndex(); - getHistory()->add (new DelHistory (idx, obj)); - m_Objects.removeAt (idx); - obj->setFile (null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool safeToCloseAll() -{ for (LDFile* f : g_loadedFiles) - if (!f->isSafeToClose()) - return false; - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::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<LDFile*> getFilesUsed (LDFile* node) -{ QList<LDFile*> 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 LDFile::closeUnused() -{ QList<LDFile*> filesUsed = getFilesUsed (LDFile::current()); - - // Anything that's explicitly opened must not be closed - for (LDFile* file : g_loadedFiles) - if (!file->isImplicit()) - filesUsed << file; - - // Remove duplicated entries - removeDuplicates (filesUsed); - - // Close all open files that aren't in filesUsed - for (LDFile* file : g_loadedFiles) - { bool isused = false; - - for (LDFile* usedFile : filesUsed) - { if (file == usedFile) - { isused = true; - break; - } - } - - if (!isused) - delete file; - } - - g_loadedFiles.clear(); - g_loadedFiles << filesUsed; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDFile::getObject (int pos) const -{ if (m_Objects.size() <= pos) - return null; - - return m_Objects[pos]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDFile::getObjectCount() const -{ return getObjects().size(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDFile::hasUnsavedChanges() const -{ return !isImplicit() && getHistory()->getPosition() != getSavePosition(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDFile::getShortName() -{ if (!getName().isEmpty()) - return basename (getName()); - - if (!getDefaultName().isEmpty()) - return getDefaultName(); - - return tr ("<anonymous>"); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> LDFile::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; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile* LDFile::current() -{ return m_curfile; -} - -// ============================================================================= -// 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 LDFile::setCurrent (LDFile* f) -{ // Implicit files were loaded for caching purposes and must never be set - // current. - if (f && f->isImplicit()) - return; - - m_curfile = f; - - if (g_win && f) - { // A ton of stuff needs to be updated - g_win->updateFileListItem (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 LDFile::countExplicitFiles() -{ int count = 0; - - for (LDFile* 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 LDFile::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 = openDATFile ("stud-logo.dat", true); - g_logoedStud2 = openDATFile ("stud2-logo.dat", true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::addToSelection (LDObject* obj) // [protected] -{ if (obj->isSelected()) - return; - - assert (obj->getFile() == this); - m_sel << obj; - obj->setSelected (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::removeFromSelection (LDObject* obj) // [protected] -{ if (!obj->isSelected()) - return; - - assert (obj->getFile() == this); - m_sel.removeOne (obj); - obj->setSelected (false); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::clearSelection() -{ for (LDObject* obj : m_sel) - removeFromSelection (obj); - - assert (m_sel.isEmpty()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const QList<LDObject*>& LDFile::getSelection() const -{ return m_sel; -} \ No newline at end of file
--- a/src/file.h Sat Dec 07 01:18:21 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,208 +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/>. - */ - -#ifndef LDFORGE_FILE_H -#define LDFORGE_FILE_H - -#include "main.h" -#include "ldtypes.h" -#include "history.h" -#include <QObject> - -#define curfile LDFile::current() - -class History; -class OpenProgressDialog; - -namespace LDPaths -{ void initPaths(); - bool tryConfigure (str path); - - str ldconfig(); - str prims(); - str parts(); - str getError(); -} - -// ============================================================================= -// LDFile -// -// The LDFile class stores a file opened in LDForge either as a editable file -// for the user or for subfile caching. Its methods handle file input and output. -// -// A file is implicit when they are opened automatically for caching purposes -// and are hidden from the user. User-opened files are explicit (not implicit). -// -// The default name is a placeholder, initially suggested name for a file. The -// primitive generator uses this to give initial names to primitives. -// ============================================================================= -class LDFile : public QObject -{ properties: - Q_OBJECT - PROPERTY (private, QList<LDObject*>, Objects, NO_OPS, STOCK_WRITE) - PROPERTY (private, History*, History, NO_OPS, STOCK_WRITE) - PROPERTY (private, QList<LDObject*>, Vertices, NO_OPS, STOCK_WRITE) - PROPERTY (public, str, Name, STR_OPS, STOCK_WRITE) - PROPERTY (public, str, DefaultName, STR_OPS, STOCK_WRITE) - PROPERTY (public, bool, Implicit, BOOL_OPS, STOCK_WRITE) - PROPERTY (public, QList<LDObject*>, Cache, NO_OPS, STOCK_WRITE) - PROPERTY (public, long, SavePosition, NUM_OPS, STOCK_WRITE) - PROPERTY (public, QListWidgetItem*, ListItem, NO_OPS, STOCK_WRITE) - - public: - LDFile(); - ~LDFile(); - - int addObject (LDObject* obj); // Adds an object to this file at the end of the file. - void addObjects (const QList<LDObject*> objs); - void clearSelection(); - void forgetObject (LDObject* obj); // Deletes the given object from the object chain. - str getShortName(); - const QList<LDObject*>& getSelection() const; - bool hasUnsavedChanges() const; // Does this file have unsaved changes? - QList<LDObject*> inlineContents (LDSubfile::InlineFlags flags); - void insertObj (int pos, LDObject* obj); - int getObjectCount() const; - LDObject* getObject (int pos) const; - bool save (str path = ""); // Saves this file to disk. - bool isSafeToClose(); // Perform safety checks. Do this before closing any files! - void setObject (int idx, LDObject* obj); - - inline LDFile& operator<< (LDObject* obj) - { addObject (obj); - return *this; - } - - inline void addHistoryStep() - { m_History->addStep(); - } - - inline void undo() - { m_History->undo(); - } - - inline void redo() - { m_History->redo(); - } - - inline void clearHistory() - { m_History->clear(); - } - - inline void addToHistory (AbstractHistoryEntry* entry) - { *m_History << entry; - } - - static void closeUnused(); - static LDFile* current(); - static void setCurrent (LDFile* f); - static void closeInitialFile(); - static int countExplicitFiles(); - - protected: - void addToSelection (LDObject* obj); - void removeFromSelection (LDObject* obj); - friend class LDObject; - - private: - QList<LDObject*> m_sel; - - static LDFile* m_curfile; -}; - -// Close all current loaded files and start off blank. -void newFile(); - -// Opens the given file as the main file. Everything is closed first. -void openMainFile (str path); - -// Finds an OpenFile by name or null if not open -LDFile* findLoadedFile (str name); - -// Opens the given file and parses the LDraw code within. Returns a pointer -// to the opened file or null on error. -LDFile* openDATFile (str path, bool search); - -// Opens the given file and returns a pointer to it, potentially looking in /parts and /p -File* openLDrawFile (str relpath, bool subdirs); - -// Close all open files, whether user-opened or subfile caches. -void closeAll(); - -// Parses a string line containing an LDraw object and returns the object parsed. -LDObject* parseLine (str line); - -// Retrieves the pointer to - or loads - the given subfile. -LDFile* getFile (str filename); - -// Re-caches all subfiles. -void reloadAllSubfiles(); - -// Is it safe to close all files? -bool safeToCloseAll(); - -QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok = null); - -extern QList<LDFile*> g_loadedFiles; - -inline const QList<LDObject*>& selection() -{ return LDFile::current()->getSelection(); -} - -void addRecentFile (str path); -void loadLogoedStuds(); -str basename (str path); -str dirname (str path); - -extern QList<LDFile*> g_loadedFiles; // Vector of all currently opened files. - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// FileLoader -// -// Loads the given file and parses it to LDObjects using parseLine. It's a -// separate class so as to be able to do the work progressively through the -// event loop, allowing the program to maintain responsivity during loading. -// ============================================================================= -class FileLoader : public QObject -{ Q_OBJECT - PROPERTY (private, QList<LDObject*>, Objects, NO_OPS, STOCK_WRITE) - PROPERTY (private, bool, Done, BOOL_OPS, STOCK_WRITE) - PROPERTY (private, int, Progress, NUM_OPS, STOCK_WRITE) - PROPERTY (private, bool, Aborted, BOOL_OPS, STOCK_WRITE) - PROPERTY (public, QStringList, Lines, NO_OPS, STOCK_WRITE) - PROPERTY (public, int*, Warnings, NO_OPS, STOCK_WRITE) - PROPERTY (public, bool, OnForeground, BOOL_OPS, STOCK_WRITE) - - public slots: - void start(); - void abort(); - - private: - OpenProgressDialog* dlg; - - private slots: - void work (int i); - - signals: - void progressUpdate (int progress); - void workDone(); -}; - -#endif // LDFORGE_FILE_H
--- a/src/gldraw.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/gldraw.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -27,7 +27,7 @@ #include "main.h" #include "config.h" -#include "file.h" +#include "document.h" #include "gldraw.h" #include "colors.h" #include "gui.h" @@ -1180,7 +1180,7 @@ // Clear the selection if we do not wish to add to it. if (!m_addpick) { QList<LDObject*> oldsel = selection(); - LDFile::current()->clearSelection(); + getCurrentDocument()->clearSelection(); for (LDObject* obj : oldsel) compileObject (obj); @@ -1314,7 +1314,7 @@ // Clear the selection when beginning to draw. QList<LDObject*> priorsel = selection(); - LDFile::current()->clearSelection(); + getCurrentDocument()->clearSelection(); for (LDObject* obj : priorsel) compileObject (obj); @@ -1328,7 +1328,7 @@ update(); } -void GLRenderer::setFile (LDFile* const& a) +void GLRenderer::setFile (LDDocument* const& a) { m_File = a; if (a != null) @@ -1411,7 +1411,7 @@ { const int segs = lores, divs = lores; // TODO: make customizable double dist0 = getCircleDrawDist (0), dist1 = getCircleDrawDist (1); - LDFile* refFile = null; + LDDocument* refFile = null; matrix transform; bool circleOrDisc = false; @@ -1420,13 +1420,13 @@ if (dist0 == dist1) { // If the radii are the same, there's no ring space to fill. Use a circle. - refFile = ::getFile ("4-4edge.dat"); + refFile = ::getDocument ("4-4edge.dat"); transform = getCircleDrawMatrix (dist0); circleOrDisc = true; } elif (dist0 == 0 || dist1 == 0) { // If either radii is 0, use a disc. - refFile = ::getFile ("4-4disc.dat"); + refFile = ::getDocument ("4-4disc.dat"); transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); circleOrDisc = true; } @@ -1435,7 +1435,7 @@ for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) { // Get a ref file for this primitive. If we cannot find it in the // LDraw library, generate it. - if ((refFile = ::getFile (radialFileName (::Ring, lores, lores, cmp.num))) == null) + if ((refFile = ::getDocument (radialFileName (::Ring, lores, lores, cmp.num))) == null) { refFile = generatePrimitive (::Ring, lores, lores, cmp.num); refFile->setImplicit (false); }
--- a/src/gldraw.h Sat Dec 07 01:18:21 2013 +0200 +++ b/src/gldraw.h Thu Dec 12 19:44:09 2013 +0200 @@ -29,7 +29,7 @@ class QDoubleSpinBox; class QSpinBox; class QLineEdit; -class LDFile; +class LDDocument; class QTimer; enum EditMode @@ -98,7 +98,7 @@ PROPERTY (public, bool, DrawOnly, BOOL_OPS, STOCK_WRITE) PROPERTY (public, MessageManager*, MessageLog, NO_OPS, STOCK_WRITE) PROPERTY (private, bool, Picking, BOOL_OPS, STOCK_WRITE) - PROPERTY (public, LDFile*, File, NO_OPS, CUSTOM_WRITE) + PROPERTY (public, LDDocument*, File, NO_OPS, CUSTOM_WRITE) PROPERTY (public, EditMode, EditMode, NO_OPS, CUSTOM_WRITE) public methods:
--- a/src/gui.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/gui.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -38,7 +38,7 @@ #include "main.h" #include "gldraw.h" #include "gui.h" -#include "file.h" +#include "document.h" #include "config.h" #include "misc.h" #include "colors.h" @@ -230,20 +230,20 @@ { str title = fmt (APPNAME " %1", fullVersionString()); // Append our current file if we have one - if (LDFile::current()) - { if (LDFile::current()->getName().length() > 0) - title += fmt (": %1", basename (LDFile::current()->getName())); + if (getCurrentDocument()) + { if (getCurrentDocument()->getName().length() > 0) + title += fmt (": %1", basename (getCurrentDocument()->getName())); else title += fmt (": <anonymous>"); - if (LDFile::current()->getObjectCount() > 0 && - LDFile::current()->getObject (0)->getType() == LDObject::Comment) + if (getCurrentDocument()->getObjectCount() > 0 && + getCurrentDocument()->getObject (0)->getType() == LDObject::Comment) { // Append title - LDComment* comm = static_cast<LDComment*> (LDFile::current()->getObject (0)); + LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0)); title += fmt (": %1", comm->text); } - if (LDFile::current()->getHistory()->getPosition() != LDFile::current()->getSavePosition()) + if (getCurrentDocument()->getHistory()->getPosition() != getCurrentDocument()->getSavePosition()) title += '*'; } @@ -275,7 +275,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- void ForgeWindow::buildObjList() -{ if (!LDFile::current()) +{ if (!getCurrentDocument()) return; // Lock the selection while we do this so that refreshing the object list @@ -288,7 +288,7 @@ ui->objectList->clear(); - for (LDObject* obj : LDFile::current()->getObjects()) + for (LDObject* obj : getCurrentDocument()->getObjects()) { str descr; switch (obj->getType()) @@ -397,7 +397,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- void ForgeWindow::slot_selectionChanged() -{ if (g_isSelectionLocked == true || LDFile::current() == null) +{ if (g_isSelectionLocked == true || getCurrentDocument() == null) return; // Update the shared selection array, though don't do this if this was @@ -409,10 +409,10 @@ QList<LDObject*> priorSelection = selection(); // Get the objects from the object list selection - LDFile::current()->clearSelection(); + getCurrentDocument()->clearSelection(); const QList<QListWidgetItem*> items = ui->objectList->selectedItems(); - for (LDObject* obj : LDFile::current()->getObjects()) + for (LDObject* obj : getCurrentDocument()->getObjects()) { for (QListWidgetItem* item : items) { if (item == obj->qObjListEntry) { obj->select(); @@ -477,7 +477,7 @@ return selection().last()->getIndex() + 1; // Otherwise place the object at the end. - return LDFile::current()->getObjectCount(); + return getCurrentDocument()->getObjectCount(); } // ============================================================================= @@ -499,7 +499,7 @@ void ForgeWindow::updateSelection() { g_isSelectionLocked = true; - for (LDObject* obj : LDFile::current()->getObjects()) + for (LDObject* obj : getCurrentDocument()->getObjects()) obj->setSelected (false); ui->objectList->clearSelection(); @@ -609,7 +609,7 @@ // ----------------------------------------------------------------------------- void ForgeWindow::deleteObjects (QList<LDObject*> objs) { for (LDObject * obj : objs) - { LDFile::current()->forgetObject (obj); + { getCurrentDocument()->forgetObject (obj); delete obj; } } @@ -619,7 +619,7 @@ void ForgeWindow::deleteByColor (const int colnum) { QList<LDObject*> objs; - for (LDObject* obj : LDFile::current()->getObjects()) + for (LDObject* obj : getCurrentDocument()->getObjects()) { if (!obj->isColored() || obj->getColor() != colnum) continue; @@ -643,7 +643,7 @@ void ForgeWindow::slot_editObject (QListWidgetItem* listitem) { LDObject* obj = null; - for (LDObject* it : LDFile::current()->getObjects()) + for (LDObject* it : getCurrentDocument()->getObjects()) { if (it->qObjListEntry == listitem) { obj = it; break; @@ -681,7 +681,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- -void ForgeWindow::save (LDFile* f, bool saveAs) +void ForgeWindow::save (LDDocument* f, bool saveAs) { str path = f->getName(); if (saveAs || path.isEmpty()) @@ -698,7 +698,7 @@ if (f->save (path)) { f->setName (path); - if (f == LDFile::current()) + if (f == getCurrentDocument()) g_win->updateTitle(); log ("Saved to %1.", path); @@ -780,7 +780,7 @@ void makeColorComboBox (QComboBox* box) { std::map<int, int> counts; - for (LDObject * obj : LDFile::current()->getObjects()) + for (LDObject * obj : getCurrentDocument()->getObjects()) { if (!obj->isColored()) continue; @@ -793,7 +793,7 @@ box->clear(); int row = 0; - for (const auto & pair : counts) + for (const std::pair<int, int>& pair : counts) { LDColor* col = getColor (pair.first); assert (col != null); @@ -817,10 +817,10 @@ #define act(N) QAction* ForgeWindow::action##N() { return ui->action##N; } #include "actions.h" -void ForgeWindow::updateFileList() +void ForgeWindow::updateDocumentList() { ui->fileList->clear(); - for (LDFile* f : g_loadedFiles) + for (LDDocument* f : g_loadedFiles) { // Don't list implicit files unless explicitly desired. if (f->isImplicit() && !gui_implicitfiles) continue; @@ -831,21 +831,21 @@ QListWidgetItem* item = ui->fileList->item (ui->fileList->count() - 1); f->setListItem (item); - updateFileListItem (f); + updateDocumentListItem (f); } } -void ForgeWindow::updateFileListItem (LDFile* f) +void ForgeWindow::updateDocumentListItem (LDDocument* f) { if (f->getListItem() == null) { // We don't have a list item for this file, so the list either doesn't // exist yet or is out of date. Build the list now. - updateFileList(); + updateDocumentList(); return; } // If this is the current file, it also needs to be the selected item on // the list. - if (f == LDFile::current()) + if (f == getCurrentDocument()) ui->fileList->setCurrentItem (f->getListItem()); // If we list implicit files, draw them with a shade of gray to make them @@ -855,34 +855,34 @@ f->getListItem()->setText (f->getShortName()); - // If the file has unsaved changes, draw a little icon next to it to mark that. + // If the document has unsaved changes, draw a little icon next to it to mark that. f->getListItem()->setIcon (f->hasUnsavedChanges() ? getIcon ("file-save") : QIcon()); } void ForgeWindow::beginAction (QAction* act) { // Open the history so we can record the edits done during this action. if (act == ui->actionUndo || act == ui->actionRedo || act == ui->actionOpen) - LDFile::current()->getHistory()->setIgnoring (true); + getCurrentDocument()->getHistory()->setIgnoring (true); } void ForgeWindow::endAction() { // Close the history now. - LDFile::current()->addHistoryStep(); + getCurrentDocument()->addHistoryStep(); // Update the list item of the current file - we may need to draw an icon // now that marks it as having unsaved changes. - updateFileListItem (LDFile::current()); + updateDocumentListItem (getCurrentDocument()); } // ============================================================================= // A file is selected from the list of files on the left of the screen. Find out // which file was picked and change to it. void ForgeWindow::changeCurrentFile() -{ LDFile* f = null; +{ LDDocument* f = null; QListWidgetItem* item = ui->fileList->currentItem(); // Find the file pointer of the item that was selected. - for (LDFile* it : g_loadedFiles) + for (LDDocument* it : g_loadedFiles) { if (it->getListItem() == item) { f = it; break; @@ -891,17 +891,17 @@ // If we picked the same file we're currently on, we don't need to do // anything. - if (!f || f == LDFile::current()) + if (!f || f == getCurrentDocument()) return; - LDFile::setCurrent (f); + LDDocument::setCurrent (f); } void ForgeWindow::refreshObjectList() { #if 0 ui->objectList->clear(); - LDFile* f = LDFile::current(); + LDDocument* f = getCurrentDocument(); for (LDObject * obj : *f) ui->objectList->addItem (obj->qObjListEntry); @@ -912,7 +912,7 @@ } void ForgeWindow::updateActions() -{ History* his = LDFile::current()->getHistory(); +{ History* his = getCurrentDocument()->getHistory(); int pos = his->getPosition(); ui->actionUndo->setEnabled (pos != -1); ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1);
--- a/src/gui.h Sat Dec 07 01:18:21 2013 +0200 +++ b/src/gui.h Thu Dec 12 19:44:09 2013 +0200 @@ -103,8 +103,8 @@ void updateSelection(); void updateGridToolBar(); void updateEditModeActions(); - void updateFileList(); - void updateFileListItem (LDFile* f); + void updateDocumentList(); + void updateDocumentListItem (LDDocument* f); int getSelectedColor(); LDObject::Type getUniformSelectedType(); void scrollToSelection(); @@ -112,7 +112,7 @@ void deleteObjects (QList<LDObject*> objs); int deleteSelection(); void deleteByColor (const int colnum); - void save (LDFile* f, bool saveAs); + void save (LDDocument* f, bool saveAs); void updateActions(); inline GLRenderer* R()
--- a/src/gui_actions.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/gui_actions.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -25,7 +25,7 @@ #include <QInputDialog> #include "gui.h" -#include "file.h" +#include "document.h" #include "history.h" #include "configDialog.h" #include "addObjectDialog.h" @@ -88,7 +88,7 @@ ui.rb_license_ca->isChecked() ? CALicense : ui.rb_license_nonca->isChecked() ? NonCALicense : ""; - LDFile::current()->addObjects ( + getCurrentDocument()->addObjects ( { new LDComment (ui.le_title->text()), new LDComment ("Name: <untitled>.dat"), new LDComment (fmt ("Author: %1", ui.le_author->text())), @@ -124,19 +124,19 @@ // ============================================================================= // ----------------------------------------------------------------------------- DEFINE_ACTION (Save, CTRL (S)) -{ g_win->save (LDFile::current(), false); +{ g_win->save (getCurrentDocument(), false); } // ============================================================================= // ----------------------------------------------------------------------------- DEFINE_ACTION (SaveAs, CTRL_SHIFT (S)) -{ g_win->save (LDFile::current(), true); +{ g_win->save (getCurrentDocument(), true); } // ============================================================================= // ----------------------------------------------------------------------------- DEFINE_ACTION (SaveAll, CTRL (L)) -{ for (LDFile* file : g_loadedFiles) +{ for (LDDocument* file : g_loadedFiles) { if (file->isImplicit()) continue; @@ -147,10 +147,10 @@ // ============================================================================= // ----------------------------------------------------------------------------- DEFINE_ACTION (Close, CTRL (W)) -{ if (!LDFile::current()->isSafeToClose()) +{ if (!getCurrentDocument()->isSafeToClose()) return; - delete LDFile::current(); + delete getCurrentDocument(); } // ============================================================================= @@ -259,7 +259,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- DEFINE_ACTION (SelectAll, CTRL (A)) -{ for (LDObject* obj : LDFile::current()->getObjects()) +{ for (LDObject* obj : getCurrentDocument()->getObjects()) obj->select(); g_win->updateSelection(); @@ -273,9 +273,9 @@ if (colnum == -1) return; // no consensus on color - LDFile::current()->clearSelection(); + getCurrentDocument()->clearSelection(); - for (LDObject* obj : LDFile::current()->getObjects()) + for (LDObject* obj : getCurrentDocument()->getObjects()) if (obj->getColor() == colnum) obj->select(); @@ -305,9 +305,9 @@ return; } - LDFile::current()->clearSelection(); + getCurrentDocument()->clearSelection(); - for (LDObject* obj : LDFile::current()->getObjects()) + for (LDObject* obj : getCurrentDocument()->getObjects()) { if (obj->getType() != type) continue; @@ -362,10 +362,10 @@ QList<LDObject*> objs = loadFileContents (&f, null); - LDFile::current()->clearSelection(); + getCurrentDocument()->clearSelection(); for (LDObject* obj : objs) - { LDFile::current()->insertObj (idx, obj); + { getCurrentDocument()->insertObj (idx, obj); obj->select(); g_win->R()->compileObject (obj); @@ -422,12 +422,12 @@ if (dlg->exec() == false) return; - LDFile::current()->clearSelection(); + getCurrentDocument()->clearSelection(); for (str line : str (te_edit->toPlainText()).split ("\n")) { LDObject* obj = parseLine (line); - LDFile::current()->insertObj (idx, obj); + getCurrentDocument()->insertObj (idx, obj); obj->select(); g_win->R()->compileObject (obj); idx++; @@ -446,7 +446,7 @@ uchar* imgdata = g_win->R()->getScreencap (w, h); QImage img = imageFromScreencap (imgdata, w, h); - str root = basename (LDFile::current()->getName()); + str root = basename (getCurrentDocument()->getName()); if (root.right (4) == ".dat") root.chop (4); @@ -560,7 +560,7 @@ // but I can't figure how to generate these pictures properly. Multi-threading // these is an immense pain. DEFINE_ACTION (testpic, "Test picture", "", "", (0)) -{ LDFile* file = getFile ("axle.dat"); +{ LDDocument* file = getFile ("axle.dat"); setlocale (LC_ALL, "C"); if (!file) @@ -625,12 +625,12 @@ defval = selection()[0]->getIndex(); int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval, - 1, LDFile::current()->getObjectCount(), 1, &ok); + 1, getCurrentDocument()->getObjectCount(), 1, &ok); - if (!ok || (obj = LDFile::current()->getObject (idx - 1)) == null) + if (!ok || (obj = getCurrentDocument()->getObject (idx - 1)) == null) return; - LDFile::current()->clearSelection(); + getCurrentDocument()->clearSelection(); obj->select(); g_win->updateSelection(); }
--- a/src/gui_editactions.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/gui_editactions.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -23,7 +23,7 @@ #include "gui.h" #include "main.h" -#include "file.h" +#include "document.h" #include "colorSelectDialog.h" #include "misc.h" #include "widgets.h" @@ -82,12 +82,12 @@ DEFINE_ACTION (Paste, CTRL (V)) { const str clipboardText = qApp->clipboard()->text(); int idx = g_win->getInsertionPoint(); - LDFile::current()->clearSelection(); + getCurrentDocument()->clearSelection(); int num = 0; for (str line : clipboardText.split ("\n")) { LDObject* pasted = parseLine (line); - LDFile::current()->insertObj (idx++, pasted); + getCurrentDocument()->insertObj (idx++, pasted); pasted->select(); g_win->R()->compileObject (pasted); ++num; @@ -135,13 +135,13 @@ delete inlineobj; LDObject* newobj = parseLine (line); - LDFile::current()->insertObj (idx++, newobj); + getCurrentDocument()->insertObj (idx++, newobj); newobj->select(); g_win->R()->compileObject (newobj); } // Delete the subfile now as it's been inlined. - LDFile::current()->forgetObject (obj); + getCurrentDocument()->forgetObject (obj); delete obj; } @@ -176,8 +176,8 @@ // Replace the quad with the first triangle and add the second triangle // after the first one. - LDFile::current()->setObject (index, triangles[0]); - LDFile::current()->insertObj (index + 1, triangles[1]); + getCurrentDocument()->setObject (index, triangles[0]); + getCurrentDocument()->insertObj (index + 1, triangles[1]); for (LDTriangle* t : triangles) g_win->R()->compileObject (t); @@ -291,7 +291,7 @@ { long idx = obj->getIndex() + i + 1; lines[i]->setColor (edgecolor); - LDFile::current()->insertObj (idx, lines[i]); + getCurrentDocument()->insertObj (idx, lines[i]); g_win->R()->compileObject (lines[i]); } @@ -318,7 +318,7 @@ vert->pos = obj->getVertex (i); vert->setColor (obj->getColor()); - LDFile::current()->insertObj (++idx, vert); + getCurrentDocument()->insertObj (++idx, vert); g_win->R()->compileObject (vert); ++num; } @@ -349,11 +349,11 @@ // ============================================================================= // ----------------------------------------------------------------------------- DEFINE_ACTION (Undo, CTRL (Z)) -{ LDFile::current()->undo(); +{ getCurrentDocument()->undo(); } DEFINE_ACTION (Redo, CTRL_SHIFT (Z)) -{ LDFile::current()->redo(); +{ getCurrentDocument()->redo(); } // ============================================================================= @@ -667,7 +667,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- static bool isColorUsed (int colnum) -{ for (LDObject* obj : LDFile::current()->getObjects()) +{ for (LDObject* obj : getCurrentDocument()->getObjects()) if (obj->isColored() && obj->getColor() == colnum) return true; @@ -726,7 +726,7 @@ // Find a spot to place the new comment for ( - obj = LDFile::current()->getObject (0); + obj = getCurrentDocument()->getObject (0); obj && obj->next() && !obj->next()->isScemantic(); obj = obj->next() ) @@ -745,12 +745,12 @@ } int idx = obj ? obj->getIndex() : 0; - LDFile::current()->insertObj (idx++, comm); + getCurrentDocument()->insertObj (idx++, comm); // If we're adding a history line right before a scemantic object, pad it // an empty line if (obj && obj->next() && obj->next()->isScemantic()) - LDFile::current()->insertObj (idx, new LDEmpty); + getCurrentDocument()->insertObj (idx, new LDEmpty); g_win->buildObjList(); delete ui;
--- a/src/history.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/history.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -18,7 +18,7 @@ #include "history.h" #include "ldtypes.h" -#include "file.h" +#include "document.h" #include "misc.h" #include "gui.h" #include "gldraw.h" @@ -116,7 +116,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- void AddHistory::undo() const -{ LDFile* f = getParent()->getFile(); +{ LDDocument* f = getParent()->getFile(); LDObject* obj = f->getObject (getIndex()); f->forgetObject (obj); delete obj; @@ -127,7 +127,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- void AddHistory::redo() const -{ LDFile* f = getParent()->getFile(); +{ LDDocument* f = getParent()->getFile(); LDObject* obj = parseLine (getCode()); f->insertObj (getIndex(), obj); g_win->R()->compileObject (obj); @@ -137,7 +137,7 @@ // heh // ----------------------------------------------------------------------------- void DelHistory::undo() const -{ LDFile* f = getParent()->getFile(); +{ LDDocument* f = getParent()->getFile(); LDObject* obj = parseLine (getCode()); f->insertObj (getIndex(), obj); g_win->R()->compileObject (obj); @@ -146,7 +146,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- void DelHistory::redo() const -{ LDFile* f = getParent()->getFile(); +{ LDDocument* f = getParent()->getFile(); LDObject* obj = f->getObject (getIndex()); f->forgetObject (obj); delete obj; @@ -157,7 +157,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- void EditHistory::undo() const -{ LDObject* obj = LDFile::current()->getObject (getIndex()); +{ LDObject* obj = getCurrentDocument()->getObject (getIndex()); LDObject* newobj = parseLine (getOldCode()); obj->replace (newobj); g_win->R()->compileObject (newobj); @@ -166,7 +166,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- void EditHistory::redo() const -{ LDObject* obj = LDFile::current()->getObject (getIndex()); +{ LDObject* obj = getCurrentDocument()->getObject (getIndex()); LDObject* newobj = parseLine (getNewCode()); obj->replace (newobj); g_win->R()->compileObject (newobj);
--- a/src/history.h Sat Dec 07 01:18:21 2013 +0200 +++ b/src/history.h Thu Dec 12 19:44:09 2013 +0200 @@ -33,7 +33,7 @@ // ============================================================================= class History { PROPERTY (private, long, Position, NUM_OPS, STOCK_WRITE) - PROPERTY (public, LDFile*, File, NO_OPS, STOCK_WRITE) + PROPERTY (public, LDDocument*, File, NO_OPS, STOCK_WRITE) PROPERTY (public, bool, Ignoring, BOOL_OPS, STOCK_WRITE) public:
--- a/src/ldconfig.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/ldconfig.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -16,7 +16,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "file.h" +#include "document.h" #include "ldconfig.h" #include "gui.h" #include "misc.h"
--- a/src/ldtypes.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/ldtypes.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -18,7 +18,7 @@ #include "main.h" #include "ldtypes.h" -#include "file.h" +#include "document.h" #include "misc.h" #include "gui.h" #include "history.h" @@ -356,7 +356,7 @@ const long end = up ? objs.size() : -1; const long incr = up ? 1 : -1; QList<LDObject*> objsToCompile; - LDFile* file = objs[0]->getFile(); + LDDocument* file = objs[0]->getFile(); for (long i = start; i != end; i += incr) { LDObject* obj = objs[i];
--- a/src/ldtypes.h Sat Dec 07 01:18:21 2013 +0200 +++ b/src/ldtypes.h Thu Dec 12 19:44:09 2013 +0200 @@ -51,7 +51,7 @@ class QListWidgetItem; class LDSubfile; -class LDFile; +class LDDocument; class LDSharedVertex; // ============================================================================= @@ -66,7 +66,7 @@ { PROPERTY (public, bool, Hidden, BOOL_OPS, STOCK_WRITE) PROPERTY (public, bool, Selected, BOOL_OPS, STOCK_WRITE) PROPERTY (public, LDObject*, Parent, NO_OPS, STOCK_WRITE) - PROPERTY (public, LDFile*, File, NO_OPS, STOCK_WRITE) + PROPERTY (public, LDDocument*, File, NO_OPS, STOCK_WRITE) PROPERTY (private, int32, ID, NUM_OPS, STOCK_WRITE) PROPERTY (public, int, Color, NUM_OPS, CUSTOM_WRITE) @@ -117,7 +117,7 @@ static str typeName (LDObject::Type type); // Get type name by enumerator static LDObject* getDefault (const LDObject::Type type); // Returns a sample object by the given enumerator - static void moveObjects (QList<LDObject*> objs, const bool up); // TODO: move this to LDFile? + static void moveObjects (QList<LDObject*> objs, const bool up); // TODO: move this to LDDocument? static str describeObjects (const QList<LDObject*>& objs); // Get a description of a list of LDObjects static LDObject* fromID (int id); @@ -318,7 +318,7 @@ LDOBJ_COLORED LDOBJ_SCEMANTIC LDOBJ_HAS_MATRIX - PROPERTY (public, LDFile*, FileInfo, NO_OPS, STOCK_WRITE) + PROPERTY (public, LDDocument*, FileInfo, NO_OPS, STOCK_WRITE) public: enum InlineFlag
--- a/src/main.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/main.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -22,7 +22,7 @@ #include <QFile> #include <QTextStream> #include "gui.h" -#include "file.h" +#include "document.h" #include "misc.h" #include "config.h" #include "colors.h" @@ -33,7 +33,7 @@ #include "dialogs.h" #include "crashcatcher.h" -QList<LDFile*> g_loadedFiles; +QList<LDDocument*> g_loadedFiles; ForgeWindow* g_win = null; const QApplication* g_app = null; File g_file_stdout (stdout, File::Write); @@ -54,7 +54,7 @@ g_app = &app; initCrashCatcher(); - LDFile::setCurrent (null); + LDDocument::setCurrent (null); // Load or create the configuration if (!Config::load())
--- a/src/primitives.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/primitives.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -22,7 +22,7 @@ #include <QThread> #include <QRegExp> #include <QFileDialog> -#include "file.h" +#include "document.h" #include "gui.h" #include "primitives.h" #include "ui_makeprim.h" @@ -498,7 +498,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- -LDFile* generatePrimitive (PrimitiveType type, int segs, int divs, int num) +LDDocument* generatePrimitive (PrimitiveType type, int segs, int divs, int num) { // Make the description str frac = str::number ((float) segs / divs); str name = radialFileName (type, segs, divs, num); @@ -522,7 +522,7 @@ if (divs == hires) descr.insert (0, "Hi-Res "); - LDFile* f = new LDFile; + LDDocument* f = new LDDocument; f->setDefaultName (name); f->addObjects ( @@ -542,9 +542,9 @@ // ============================================================================= // ----------------------------------------------------------------------------- -LDFile* getPrimitive (PrimitiveType type, int segs, int divs, int num) +LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num) { str name = radialFileName (type, segs, divs, num); - LDFile* f = getFile (name); + LDDocument* f = getDocument (name); if (f != null) return f; @@ -596,7 +596,7 @@ dlg->ui->rb_ndisc->isChecked() ? DiscNeg : dlg->ui->rb_ring->isChecked() ? Ring : Cone; - LDFile* f = generatePrimitive (type, segs, divs, num); + LDDocument* f = generatePrimitive (type, segs, divs, num); g_win->save (f, false); delete f;
--- a/src/primitives.h Sat Dec 07 01:18:21 2013 +0200 +++ b/src/primitives.h Thu Dec 12 19:44:09 2013 +0200 @@ -24,7 +24,7 @@ #include <QRegExp> #include <QDialog> -class LDFile; +class LDDocument; class Ui_MakePrimUI; class PrimitiveCategory; struct Primitive @@ -105,11 +105,11 @@ }; void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines); -LDFile* generatePrimitive (PrimitiveType type, int segs, int divs, int num); +LDDocument* generatePrimitive (PrimitiveType type, int segs, int divs, int num); // Gets a primitive by the given specs. If the primitive cannot be found, it will // be automatically generated. -LDFile* getPrimitive (PrimitiveType type, int segs, int divs, int num); +LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num); str radialFileName (PrimitiveType type, int segs, int divs, int num);
--- a/src/types.cpp Sat Dec 07 01:18:21 2013 +0200 +++ b/src/types.cpp Thu Dec 12 19:44:09 2013 +0200 @@ -25,7 +25,7 @@ #include "types.h" #include "misc.h" #include "ldtypes.h" -#include "file.h" +#include "document.h" // ============================================================================= // ----------------------------------------------------------------------------- @@ -510,10 +510,10 @@ void LDBoundingBox::calculate() { reset(); - if (!LDFile::current()) + if (!getCurrentDocument()) return; - for (LDObject* obj : LDFile::current()->getObjects()) + for (LDObject* obj : getCurrentDocument()->getObjects()) calcObject (obj); }