- LDFile renamed to LDDocument, file.h -> document.h

Thu, 12 Dec 2013 19:44:09 +0200

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Thu, 12 Dec 2013 19:44:09 +0200
changeset 553
2418d5955421
parent 552
454f8b730946
child 554
2478a6b3106d

- LDFile renamed to LDDocument, file.h -> document.h
- Added the INSTALL document

INSTALL file | annotate | diff | comparison | revisions
src/addObjectDialog.cpp file | annotate | diff | comparison | revisions
src/colors.cpp file | annotate | diff | comparison | revisions
src/config.cpp file | annotate | diff | comparison | revisions
src/configDialog.cpp file | annotate | diff | comparison | revisions
src/dialogs.cpp file | annotate | diff | comparison | revisions
src/document.cpp file | annotate | diff | comparison | revisions
src/document.h file | annotate | diff | comparison | revisions
src/download.cpp file | annotate | diff | comparison | revisions
src/download.h file | annotate | diff | comparison | revisions
src/extprogs.cpp file | annotate | diff | comparison | revisions
src/file.cpp file | annotate | diff | comparison | revisions
src/file.h file | annotate | diff | comparison | revisions
src/gldraw.cpp file | annotate | diff | comparison | revisions
src/gldraw.h file | annotate | diff | comparison | revisions
src/gui.cpp file | annotate | diff | comparison | revisions
src/gui.h file | annotate | diff | comparison | revisions
src/gui_actions.cpp file | annotate | diff | comparison | revisions
src/gui_editactions.cpp file | annotate | diff | comparison | revisions
src/history.cpp file | annotate | diff | comparison | revisions
src/history.h file | annotate | diff | comparison | revisions
src/ldconfig.cpp file | annotate | diff | comparison | revisions
src/ldtypes.cpp file | annotate | diff | comparison | revisions
src/ldtypes.h file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/primitives.cpp file | annotate | diff | comparison | revisions
src/primitives.h file | annotate | diff | comparison | revisions
src/types.cpp file | annotate | diff | comparison | revisions
--- /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);
 }
 

mercurial