- renamed files to camelCase

Sat, 29 Mar 2014 05:26:10 +0200

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Sat, 29 Mar 2014 05:26:10 +0200
changeset 655
b376645315ab
parent 654
a74f2ff353b8
child 656
2a1c204df14d
child 706
d79083b9f74d

- renamed files to camelCase

CMakeLists.txt file | annotate | diff | comparison | revisions
src/AddObjectDialog.cc file | annotate | diff | comparison | revisions
src/AddObjectDialog.h file | annotate | diff | comparison | revisions
src/ColorSelector.cc file | annotate | diff | comparison | revisions
src/ColorSelector.h file | annotate | diff | comparison | revisions
src/Colors.cc file | annotate | diff | comparison | revisions
src/Colors.h file | annotate | diff | comparison | revisions
src/Configuration.cc file | annotate | diff | comparison | revisions
src/Configuration.h file | annotate | diff | comparison | revisions
src/ConfigurationDialog.cc file | annotate | diff | comparison | revisions
src/ConfigurationDialog.h file | annotate | diff | comparison | revisions
src/CrashCatcher.cc file | annotate | diff | comparison | revisions
src/CrashCatcher.h file | annotate | diff | comparison | revisions
src/Dialogs.cc file | annotate | diff | comparison | revisions
src/Dialogs.h file | annotate | diff | comparison | revisions
src/Document.cc file | annotate | diff | comparison | revisions
src/Document.h file | annotate | diff | comparison | revisions
src/Documentation.cc file | annotate | diff | comparison | revisions
src/Documentation.h file | annotate | diff | comparison | revisions
src/EditHistory.cc file | annotate | diff | comparison | revisions
src/EditHistory.h file | annotate | diff | comparison | revisions
src/ExternalPrograms.cc file | annotate | diff | comparison | revisions
src/Format.h file | annotate | diff | comparison | revisions
src/GLRenderer.cc file | annotate | diff | comparison | revisions
src/GLRenderer.h file | annotate | diff | comparison | revisions
src/LDConfig.cc file | annotate | diff | comparison | revisions
src/LDConfig.h file | annotate | diff | comparison | revisions
src/LDObject.cc file | annotate | diff | comparison | revisions
src/LDObject.h file | annotate | diff | comparison | revisions
src/Macros.h file | annotate | diff | comparison | revisions
src/Main.cc file | annotate | diff | comparison | revisions
src/Main.h file | annotate | diff | comparison | revisions
src/MainWindow.cc file | annotate | diff | comparison | revisions
src/MainWindow.h file | annotate | diff | comparison | revisions
src/MessageLog.cc file | annotate | diff | comparison | revisions
src/MessageLog.h file | annotate | diff | comparison | revisions
src/Misc.cc file | annotate | diff | comparison | revisions
src/Misc.h file | annotate | diff | comparison | revisions
src/PartDownloader.cc file | annotate | diff | comparison | revisions
src/PartDownloader.h file | annotate | diff | comparison | revisions
src/Primitives.cc file | annotate | diff | comparison | revisions
src/Primitives.h file | annotate | diff | comparison | revisions
src/Types.cc file | annotate | diff | comparison | revisions
src/Types.h file | annotate | diff | comparison | revisions
src/Version.cc file | annotate | diff | comparison | revisions
src/Version.h file | annotate | diff | comparison | revisions
src/Widgets.cc file | annotate | diff | comparison | revisions
src/Widgets.h file | annotate | diff | comparison | revisions
src/actions.cc file | annotate | diff | comparison | revisions
src/actions/EditActions.cc file | annotate | diff | comparison | revisions
src/actions/MainActions.cc file | annotate | diff | comparison | revisions
src/actionsEdit.cc file | annotate | diff | comparison | revisions
src/addObjectDialog.cc file | annotate | diff | comparison | revisions
src/addObjectDialog.h file | annotate | diff | comparison | revisions
src/basics.cc file | annotate | diff | comparison | revisions
src/basics.h file | annotate | diff | comparison | revisions
src/colorSelector.cc file | annotate | diff | comparison | revisions
src/colorSelector.h file | annotate | diff | comparison | revisions
src/colors.cc file | annotate | diff | comparison | revisions
src/colors.h file | annotate | diff | comparison | revisions
src/configDialog.cc file | annotate | diff | comparison | revisions
src/configDialog.h file | annotate | diff | comparison | revisions
src/configuration.cc file | annotate | diff | comparison | revisions
src/configuration.h file | annotate | diff | comparison | revisions
src/crashCatcher.cc file | annotate | diff | comparison | revisions
src/crashCatcher.h file | annotate | diff | comparison | revisions
src/dialogs.cc file | annotate | diff | comparison | revisions
src/dialogs.h file | annotate | diff | comparison | revisions
src/documentation.cc file | annotate | diff | comparison | revisions
src/documentation.h file | annotate | diff | comparison | revisions
src/editHistory.cc file | annotate | diff | comparison | revisions
src/editHistory.h file | annotate | diff | comparison | revisions
src/extPrograms.cc file | annotate | diff | comparison | revisions
src/format.h file | annotate | diff | comparison | revisions
src/glRenderer.cc file | annotate | diff | comparison | revisions
src/glRenderer.h file | annotate | diff | comparison | revisions
src/ldConfig.cc file | annotate | diff | comparison | revisions
src/ldConfig.h file | annotate | diff | comparison | revisions
src/ldDocument.cc file | annotate | diff | comparison | revisions
src/ldDocument.h file | annotate | diff | comparison | revisions
src/ldObject.cc file | annotate | diff | comparison | revisions
src/ldObject.h file | annotate | diff | comparison | revisions
src/macros.h file | annotate | diff | comparison | revisions
src/main.cc file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/mainWindow.cc file | annotate | diff | comparison | revisions
src/mainWindow.h file | annotate | diff | comparison | revisions
src/messageLog.cc file | annotate | diff | comparison | revisions
src/messageLog.h file | annotate | diff | comparison | revisions
src/misc/DocumentPointer.cc file | annotate | diff | comparison | revisions
src/misc/DocumentPointer.h file | annotate | diff | comparison | revisions
src/misc/InvokationDeferer.cc file | annotate | diff | comparison | revisions
src/misc/InvokationDeferer.h file | annotate | diff | comparison | revisions
src/misc/RingFinder.cc file | annotate | diff | comparison | revisions
src/misc/RingFinder.h file | annotate | diff | comparison | revisions
src/misc/documentPointer.cc file | annotate | diff | comparison | revisions
src/misc/documentPointer.h file | annotate | diff | comparison | revisions
src/misc/invokeLater.cc file | annotate | diff | comparison | revisions
src/misc/invokeLater.h file | annotate | diff | comparison | revisions
src/misc/ringFinder.cc file | annotate | diff | comparison | revisions
src/misc/ringFinder.h file | annotate | diff | comparison | revisions
src/miscallenous.cc file | annotate | diff | comparison | revisions
src/miscallenous.h file | annotate | diff | comparison | revisions
src/partDownloader.cc file | annotate | diff | comparison | revisions
src/partDownloader.h file | annotate | diff | comparison | revisions
src/primitives.cc file | annotate | diff | comparison | revisions
src/primitives.h file | annotate | diff | comparison | revisions
src/radioGroup.cc file | annotate | diff | comparison | revisions
src/radioGroup.h file | annotate | diff | comparison | revisions
src/version.cc file | annotate | diff | comparison | revisions
src/version.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Wed Mar 12 16:20:40 2014 +0200
+++ b/CMakeLists.txt	Sat Mar 29 05:26:10 2014 +0200
@@ -12,67 +12,67 @@
 get_target_property (UPDATEREVISION_EXE updaterevision LOCATION)
 
 add_custom_target (revision_check ALL
-    COMMAND ${UPDATEREVISION_EXE} src/Git.h
+    COMMAND ${UPDATEREVISION_EXE} src/gitinfo.h
     WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
     DEPENDS updaterevision)
 
 include_directories (${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
 
 set (LDForgeSources
-	src/AddObjectDialog.cc
-	src/Colors.cc
-	src/ColorSelector.cc
-	src/Configuration.cc
-	src/ConfigurationDialog.cc
-	src/CrashCatcher.cc
-	src/Dialogs.cc
-	src/Documentation.cc
-	src/Document.cc
-	src/EditHistory.cc
-	src/ExternalPrograms.cc
-	src/GLRenderer.cc
-	src/LDConfig.cc
-	src/LDObject.cc
-	src/Main.cc
-	src/MainWindow.cc
-	src/MessageLog.cc
-	src/Misc.cc
-	src/PartDownloader.cc
-	src/Primitives.cc
-	src/Types.cc
-	src/Version.cc
-	src/Widgets.cc
-	src/actions/MainActions.cc
-	src/actions/EditActions.cc
+	src/actions.cc
+	src/actionsEdit.cc
+	src/addObjectDialog.cc
+	src/basics.cc
+	src/colors.cc
+	src/colorSelector.cc
+	src/configuration.cc
+	src/configDialog.cc
+	src/crashCatcher.cc
+	src/dialogs.cc
+	src/documentation.cc
+	src/editHistory.cc
+	src/extPrograms.cc
+	src/glRenderer.cc
+	src/ldConfig.cc
+	src/ldDocument.cc
+	src/ldObject.cc
+	src/main.cc
+	src/mainWindow.cc
+	src/messageLog.cc
+	src/miscallenous.cc
+	src/partDownloader.cc
+	src/primitives.cc
+	src/radioGroup.cc
+	src/version.cc
 )
 
 set (LDForgeHeaders
-	src/Macros.h
-	src/CrashCatcher.h
-	src/Colors.h
-	src/misc/DocumentPointer.h
-	src/misc/InvokationDeferer.h
-	src/misc/RingFinder.h
-	src/Document.h
-	src/AddObjectDialog.h
-	src/LDConfig.h
-	src/PartDownloader.h
-	src/LDObject.h
-	src/Primitives.h
-	src/Misc.h
-	src/MessageLog.h
-	src/Dialogs.h
-	src/Widgets.h
-	src/Documentation.h
-	src/Main.h
-	src/Types.h
-	src/ColorSelector.h
-	src/ConfigurationDialog.h
-	src/GLRenderer.h
-	src/Configuration.h
-	src/MainWindow.h
-	src/EditHistory.h
-	src/Format.h
+	src/macros.h
+	src/crashCatcher.h
+	src/colors.h
+	src/misc/documentPointer.h
+	src/misc/invokeLater.h
+	src/misc/ringFinder.h
+	src/ldDocument.h
+	src/addObjectDialog.h
+	src/ldConfig.h
+	src/partDownloader.h
+	src/ldObject.h
+	src/primitives.h
+	src/miscallenous.h
+	src/messageLog.h
+	src/dialogs.h
+	src/radioGroup.h
+	src/documentation.h
+	src/main.h
+	src/basics.h
+	src/colorSelector.h
+	src/configDialog.h
+	src/glRenderer.h
+	src/configuration.h
+	src/mainWindow.h
+	src/editHistory.h
+	src/format.h
 )
 
 set (LDForgeForms
--- a/src/AddObjectDialog.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,443 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QGridLayout>
-#include <QCheckBox>
-#include <QDialogButtonBox>
-#include <QSpinBox>
-#include <QLabel>
-#include <QListWidget>
-#include <QTreeWidget>
-#include <QLineEdit>
-#include <QPushButton>
-#include "MainWindow.h"
-#include "AddObjectDialog.h"
-#include "Document.h"
-#include "Colors.h"
-#include "ColorSelector.h"
-#include "EditHistory.h"
-#include "Widgets.h"
-#include "Misc.h"
-#include "Primitives.h"
-
-// =============================================================================
-//
-class SubfileListItem : public QTreeWidgetItem
-{
-	PROPERTY (public, Primitive*,	primitive, setPrimitive, STOCK_WRITE)
-
-	public:
-		SubfileListItem (QTreeWidgetItem* parent, Primitive* info) :
-			QTreeWidgetItem (parent),
-			m_primitive (info) {}
-
-		SubfileListItem (QTreeWidget* parent, Primitive* info) :
-			QTreeWidgetItem (parent),
-			m_primitive (info) {}
-};
-
-// =============================================================================
-//
-AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) :
-	QDialog (parent)
-{
-	setlocale (LC_ALL, "C");
-
-	int coordCount = 0;
-	QString typeName = LDObject::typeName (type);
-
-	switch (type)
-	{
-		case LDObject::EComment:
-		{
-			le_comment = new QLineEdit;
-
-			if (obj)
-				le_comment->setText (static_cast<LDComment*> (obj)->text());
-
-			le_comment->setMinimumWidth (384);
-		} break;
-
-		case LDObject::ELine:
-		{
-			coordCount = 6;
-		} break;
-
-		case LDObject::ETriangle:
-		{
-			coordCount = 9;
-		} break;
-
-		case LDObject::EQuad:
-		case LDObject::ECondLine:
-		{
-			coordCount = 12;
-		} break;
-
-		case LDObject::EVertex:
-		{
-			coordCount = 3;
-		} break;
-
-		case LDObject::EBFC:
-		{
-			rb_bfcType = new RadioGroup ("Statement", {}, 0, Qt::Vertical);
-
-			for (int i = 0; i < LDBFC::NumStatements; ++i)
-			{
-				// Separate these in two columns
-				if (i == LDBFC::NumStatements / 2)
-					rb_bfcType->rowBreak();
-
-				rb_bfcType->addButton (LDBFC::k_statementStrings[i]);
-			}
-
-			if (obj)
-				rb_bfcType->setValue ( (int) static_cast<LDBFC*> (obj)->statement());
-		} break;
-
-		case LDObject::ESubfile:
-		{
-			coordCount = 3;
-			tw_subfileList = new QTreeWidget();
-			tw_subfileList->setHeaderLabel (tr ("Primitives"));
-
-			for (PrimitiveCategory* cat : g_PrimitiveCategories)
-			{
-				SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, null);
-				parentItem->setText (0, cat->name());
-				QList<QTreeWidgetItem*> subfileItems;
-
-				for (Primitive& prim : cat->prims)
-				{
-					SubfileListItem* item = new SubfileListItem (parentItem, &prim);
-					item->setText (0, format ("%1 - %2", prim.name, prim.title));
-					subfileItems << item;
-
-					// If this primitive is the one the current object points to,
-					// select it by default
-					if (obj && static_cast<LDSubfile*> (obj)->fileInfo()->name() == prim.name)
-						tw_subfileList->setCurrentItem (item);
-				}
-
-				tw_subfileList->addTopLevelItem (parentItem);
-			}
-
-			connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged()));
-			lb_subfileName = new QLabel ("File:");
-			le_subfileName = new QLineEdit;
-			le_subfileName->setFocus();
-
-			if (obj)
-			{
-				LDSubfile* ref = static_cast<LDSubfile*> (obj);
-				le_subfileName->setText (ref->fileInfo()->name());
-			}
-		} break;
-
-		default:
-		{
-			critical (format ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName));
-		} return;
-	}
-
-	QPixmap icon = getIcon (format ("add-%1", typeName));
-	LDObject* defaults = LDObject::getDefault (type);
-
-	lb_typeIcon = new QLabel;
-	lb_typeIcon->setPixmap (icon);
-
-	// Show a color edit dialog for the types that actually use the color
-	if (defaults->isColored())
-	{
-		if (obj != null)
-			colnum = obj->color();
-		else
-			colnum = (type == LDObject::ECondLine || type == LDObject::ELine) ? edgecolor : maincolor;
-
-		pb_color = new QPushButton;
-		setButtonBackground (pb_color, colnum);
-		connect (pb_color, SIGNAL (clicked()), this, SLOT (slot_colorButtonClicked()));
-	}
-
-	for (int i = 0; i < coordCount; ++i)
-	{
-		dsb_coords[i] = new QDoubleSpinBox;
-		dsb_coords[i]->setDecimals (5);
-		dsb_coords[i]->setMinimum (-10000.0);
-		dsb_coords[i]->setMaximum (10000.0);
-	}
-
-	QGridLayout* const layout = new QGridLayout;
-	layout->addWidget (lb_typeIcon, 0, 0);
-
-	switch (type)
-	{
-		case LDObject::ELine:
-		case LDObject::ECondLine:
-		case LDObject::ETriangle:
-		case LDObject::EQuad:
-
-			// Apply coordinates
-			if (obj)
-			{
-				for (int i = 0; i < coordCount / 3; ++i)
-					for (int j = 0; j < 3; ++j)
-						dsb_coords[ (i * 3) + j]->setValue (obj->vertex (i).getCoordinate (j));
-			}
-
-			break;
-
-		case LDObject::EComment:
-			layout->addWidget (le_comment, 0, 1);
-			break;
-
-		case LDObject::EBFC:
-			layout->addWidget (rb_bfcType, 0, 1);
-			break;
-
-		case LDObject::ESubfile:
-			layout->addWidget (tw_subfileList, 1, 1, 1, 2);
-			layout->addWidget (lb_subfileName, 2, 1);
-			layout->addWidget (le_subfileName, 2, 2);
-			break;
-
-		default:
-			break;
-	}
-
-	if (defaults->hasMatrix())
-	{
-		LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
-
-		QLabel* lb_matrix = new QLabel ("Matrix:");
-		le_matrix = new QLineEdit;
-		// le_matrix->setValidator (new QDoubleValidator);
-		Matrix defaultMatrix = g_identity;
-
-		if (mo)
-		{
-			for_axes (ax)
-				dsb_coords[ax]->setValue (mo->position()[ax]);
-
-			defaultMatrix = mo->transform();
-		}
-
-		le_matrix->setText (defaultMatrix.toString());
-		layout->addWidget (lb_matrix, 4, 1);
-		layout->addWidget (le_matrix, 4, 2, 1, 3);
-	}
-
-	if (defaults->isColored())
-		layout->addWidget (pb_color, 1, 0);
-
-	if (coordCount > 0)
-	{
-		QGridLayout* const qCoordLayout = new QGridLayout;
-
-		for (int i = 0; i < coordCount; ++i)
-			qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3));
-
-		layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3);
-	}
-
-	QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-	QWidget::connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept()));
-	QWidget::connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject()));
-	layout->addWidget (bbx_buttons, 5, 0, 1, 4);
-	setLayout (layout);
-	setWindowTitle (format (tr ("Edit %1"), typeName));
-
-	setWindowIcon (icon);
-	defaults->destroy();
-}
-
-// =============================================================================
-// =============================================================================
-void AddObjectDialog::setButtonBackground (QPushButton* button, int colnum)
-{
-	LDColor* col = ::getColor (colnum);
-
-	button->setIcon (getIcon ("palette"));
-	button->setAutoFillBackground (true);
-
-	if (col)
-		button->setStyleSheet (format ("background-color: %1", col->hexcode));
-}
-
-// =============================================================================
-// =============================================================================
-QString AddObjectDialog::currentSubfileName()
-{
-	SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem());
-
-	if (item->primitive() == null)
-		return ""; // selected a heading
-
-	return item->primitive()->name;
-}
-
-// =============================================================================
-// =============================================================================
-void AddObjectDialog::slot_colorButtonClicked()
-{
-	ColorSelector::selectColor (colnum, colnum, this);
-	setButtonBackground (pb_color, colnum);
-}
-
-// =============================================================================
-// =============================================================================
-void AddObjectDialog::slot_subfileTypeChanged()
-{
-	QString name = currentSubfileName();
-
-	if (name.length() > 0)
-		le_subfileName->setText (name);
-}
-
-// =============================================================================
-// =============================================================================
-template<class T> static T* initObj (LDObject*& obj)
-{
-	if (obj == null)
-		obj = new T;
-
-	return static_cast<T*> (obj);
-}
-
-// =============================================================================
-// =============================================================================
-void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj)
-{
-	setlocale (LC_ALL, "C");
-
-	// FIXME: Redirect to Edit Raw
-	if (obj && obj->type() == LDObject::EError)
-		return;
-
-	if (type == LDObject::EEmpty)
-		return; // Nothing to edit with empties
-
-	const bool newObject = (obj == null);
-	Matrix transform = g_identity;
-	AddObjectDialog dlg (type, obj);
-
-	assert (obj == null || obj->type() == type);
-
-	if (dlg.exec() == false)
-		return;
-
-	if (type == LDObject::ESubfile)
-	{
-		QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts);
-
-		if (matrixstrvals.size() == 9)
-		{
-			double matrixvals[9];
-			int i = 0;
-
-			for (QString val : matrixstrvals)
-				matrixvals[i++] = val.toFloat();
-
-			transform = Matrix (matrixvals);
-		}
-	}
-
-	switch (type)
-	{
-		case LDObject::EComment:
-		{
-			LDComment* comm = initObj<LDComment> (obj);
-			comm->setText (dlg.le_comment->text());
-		}
-		break;
-
-		case LDObject::ELine:
-		case LDObject::ETriangle:
-		case LDObject::EQuad:
-		case LDObject::ECondLine:
-		{
-			if (!obj)
-				obj = LDObject::getDefault (type);
-
-			for (int i = 0; i < obj->vertices(); ++i)
-			{
-				Vertex v;
-
-				for_axes (ax)
-					v[ax] = dlg.dsb_coords[ (i * 3) + ax]->value();
-
-				obj->setVertex (i, v);
-			}
-		} break;
-
-		case LDObject::EBFC:
-		{
-			LDBFC* bfc = initObj<LDBFC> (obj);
-			bfc->setStatement ((LDBFC::Statement) dlg.rb_bfcType->value());
-		} break;
-
-		case LDObject::EVertex:
-		{
-			LDVertex* vert = initObj<LDVertex> (obj);
-
-			for_axes (ax)
-				vert->pos[ax] = dlg.dsb_coords[ax]->value();
-		}
-		break;
-
-		case LDObject::ESubfile:
-		{
-			QString name = dlg.le_subfileName->text();
-
-			if (name.length() == 0)
-				return; // no subfile filename
-
-			LDDocument* file = getDocument (name);
-
-			if (!file)
-			{
-				critical (format ("Couldn't open `%1': %2", name, strerror (errno)));
-				return;
-			}
-
-			LDSubfile* ref = initObj<LDSubfile> (obj);
-			assert (ref);
-
-			for_axes (ax)
-				ref->setCoordinate (ax, dlg.dsb_coords[ax]->value());
-
-			ref->setTransform (transform);
-			ref->setFileInfo (file);
-		} break;
-
-		default:
-			break;
-	}
-
-	if (obj->isColored())
-		obj->setColor (dlg.colnum);
-
-	if (newObject)
-	{
-		int idx = g_win->getInsertionPoint();
-		getCurrentDocument()->insertObj (idx, obj);
-	}
-
-	g_win->refresh();
-}
--- a/src/AddObjectDialog.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QDialog>
-#include "LDObject.h"
-
-class QTreeWidgetItem;
-class QLineEdit;
-class RadioGroup;
-class QCheckBox;
-class QSpinBox;
-class QLabel;
-class QTreeWidget;
-class QDoubleSpinBox;
-
-class AddObjectDialog : public QDialog
-{
-	Q_OBJECT
-
-	public:
-		AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent = null);
-		static void staticDialog (const LDObject::Type type, LDObject* obj);
-
-		QLabel* lb_typeIcon;
-
-		// Comment line edit
-		QLineEdit* le_comment;
-
-		// Coordinate edits for.. anything with coordinates, really.
-		QDoubleSpinBox* dsb_coords[12];
-
-		// Color selection dialog button
-		QPushButton* pb_color;
-
-		// BFC-related widgets
-		RadioGroup* rb_bfcType;
-
-		// Subfile stuff
-		QTreeWidget* tw_subfileList;
-		QLineEdit* le_subfileName;
-		QLabel* lb_subfileName;
-		QLineEdit* le_matrix;
-
-	private:
-		void setButtonBackground (QPushButton* button, int color);
-		QString currentSubfileName();
-
-		int colnum;
-
-	private slots:
-		void slot_colorButtonClicked();
-		void slot_subfileTypeChanged();
-};
--- a/src/ColorSelector.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,210 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  colorSelectDialog.cxx: Color selector box.
- */
-
-#include <QGraphicsScene>
-#include <QGraphicsItem>
-#include <QMouseEvent>
-#include <QScrollBar>
-
-#include "Main.h"
-#include "MainWindow.h"
-#include "ColorSelector.h"
-#include "Colors.h"
-#include "Configuration.h"
-#include "Misc.h"
-#include "ui_colorsel.h"
-
-static const int g_numColumns = 16;
-static const int g_squareSize = 32;
-
-extern_cfg (String, gl_maincolor);
-extern_cfg (Float, gl_maincolor_alpha);
-
-// =============================================================================
-// =============================================================================
-ColorSelector::ColorSelector (int defval, QWidget* parent) : QDialog (parent)
-{
-	// Remove the default color if it's invalid
-	if (!getColor (defval))
-		defval = -1;
-
-	m_firstResize = true;
-	ui = new Ui_ColorSelUI;
-	ui->setupUi (this);
-
-	m_scene = new QGraphicsScene;
-	ui->viewport->setScene (m_scene);
-	setSelection (getColor (defval));
-
-	// not really an icon but eh
-	m_scene->setBackgroundBrush (getIcon ("checkerboard"));
-	drawScene();
-
-	int width = viewportWidth();
-	ui->viewport->setMinimumWidth (width);
-	ui->viewport->setMaximumWidth (width);
-
-	drawColorInfo();
-}
-
-// =============================================================================
-// =============================================================================
-ColorSelector::~ColorSelector()
-{
-	delete ui;
-}
-
-// =============================================================================
-// =============================================================================
-void ColorSelector::drawScene()
-{
-	const int numCols = g_numColumns;
-	const int square = g_squareSize;
-	const int g_maxHeight = (numRows() * square);
-	QRect sceneRect (0, 0, viewportWidth(), g_maxHeight);
-
-	m_scene->setSceneRect (sceneRect);
-	ui->viewport->setSceneRect (sceneRect);
-
-	const double penWidth = 1.0f;
-
-	// Draw the color rectangles.
-	m_scene->clear();
-
-	for (int i = 0; i < MAX_COLORS; ++i)
-	{
-		LDColor* info = ::getColor (i);
-
-		if (!info)
-			continue;
-
-		const double x = (i % numCols) * square;
-		const double y = (i / numCols) * square;
-		const double w = square - (penWidth / 2);
-
-		QColor col = info->faceColor;
-
-		if (i == maincolor)
-		{
-			// Use the user preferences for main color here
-			col = QColor (gl_maincolor);
-			col.setAlpha (gl_maincolor_alpha * 255.0f);
-		}
-
-		QPen pen (info->edgeColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
-		m_scene->addRect (x, y, w, w, pen, col);
-		QGraphicsTextItem* numtext = m_scene->addText (format ("%1", i));
-		numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black);
-		numtext->setPos (x, y);
-
-		if (selection() && i == selection()->index)
-		{
-			auto curspic = m_scene->addPixmap (getIcon ("colorcursor"));
-			curspic->setPos (x, y);
-		}
-	}
-}
-
-// =============================================================================
-// =============================================================================
-int ColorSelector::numRows() const
-{
-	return (MAX_COLORS / g_numColumns);
-}
-
-// =============================================================================
-// =============================================================================
-int ColorSelector::viewportWidth() const
-{
-	return g_numColumns * g_squareSize + 21;
-}
-
-// =============================================================================
-// =============================================================================
-void ColorSelector::drawColorInfo()
-{
-	if (!selection())
-	{
-		ui->colorLabel->setText ("---");
-		return;
-	}
-
-	ui->colorLabel->setText (format ("%1 - %2", selection()->index, selection()->name));
-}
-
-// =============================================================================
-// =============================================================================
-void ColorSelector::resizeEvent (QResizeEvent* ev)
-{
-	// If this is the first resize, check if we need to scroll down to see the
-	// currently selected color. We cannot do this in the constructor because the
-	// height is not set properly there.
-	if (m_firstResize)
-	{
-		int visibleColors = (ui->viewport->height() / g_squareSize) * g_numColumns;
-
-		if (selection() && selection()->index >= visibleColors)
-		{
-			int y = (selection()->index / g_numColumns) * g_squareSize;
-			ui->viewport->verticalScrollBar()->setValue (y);
-		}
-
-		m_firstResize = false;
-	}
-
-	(void) ev;
-	drawScene();
-}
-
-// =============================================================================
-// =============================================================================
-void ColorSelector::mousePressEvent (QMouseEvent* event)
-{
-	QPointF scenepos = ui->viewport->mapToScene (event->pos());
-
-	int x = (scenepos.x() - (g_squareSize / 2)) / g_squareSize;
-	int y = (scenepos.y() - (g_squareSize / 2)) / g_squareSize;
-	int idx = (y * g_numColumns) + x;
-
-	LDColor* col = ::getColor (idx);
-
-	if (!col)
-		return;
-
-	setSelection (col);
-	drawScene();
-	drawColorInfo();
-}
-
-// =============================================================================
-// =============================================================================
-bool ColorSelector::selectColor (int& val, int defval, QWidget* parent)
-{
-	ColorSelector dlg (defval, parent);
-
-	if (dlg.exec() && dlg.selection() != null)
-	{
-		val = dlg.selection()->index;
-		return true;
-	}
-
-	return false;
-}
\ No newline at end of file
--- a/src/ColorSelector.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QDialog>
-#include "Main.h"
-
-class LDColor;
-class Ui_ColorSelUI;
-class QGraphicsScene;
-
-class ColorSelector : public QDialog
-{
-	Q_OBJECT
-	PROPERTY (private,	LDColor*,	selection,	setSelection,	STOCK_WRITE)
-
-	public:
-		explicit ColorSelector (int defval = -1, QWidget* parent = null);
-		virtual ~ColorSelector();
-		static bool selectColor (int& val, int defval = -1, QWidget* parent = null);
-
-	protected:
-		void mousePressEvent (QMouseEvent* event);
-		void resizeEvent (QResizeEvent* ev);
-
-	private:
-		Ui_ColorSelUI* ui;
-		QGraphicsScene* m_scene;
-		bool m_firstResize;
-
-		int numRows() const;
-		int viewportWidth() const;
-		void drawScene();
-		void drawColorInfo();
-};
--- a/src/Colors.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  colors.cxx: LDraw color management. LDConfig.ldr parsing is not here!
- *  TODO: Make LDColor more full-fledged, add support for direct colors.
- *  TODO: g_LDColors should probably be a map.
- */
-
-#include "Main.h"
-#include "Colors.h"
-#include "Document.h"
-#include "Misc.h"
-#include "MainWindow.h"
-#include "LDConfig.h"
-#include <QColor>
-
-static LDColor* g_LDColors[MAX_COLORS];
-
-// =============================================================================
-// =============================================================================
-void initColors()
-{
-	LDColor* col;
-	print ("Initializing color information.\n");
-
-	// Always make sure there's 16 and 24 available. They're special like that.
-	col = new LDColor;
-	col->faceColor = col->hexcode = "#AAAAAA";
-	col->edgeColor = Qt::black;
-	g_LDColors[maincolor] = col;
-
-	col = new LDColor;
-	col->faceColor = col->edgeColor = col->hexcode = "#000000";
-	g_LDColors[edgecolor] = col;
-
-	parseLDConfig();
-}
-
-// =============================================================================
-// =============================================================================
-LDColor* getColor (int colnum)
-{
-	// Check bounds
-	if (colnum < 0 || colnum >= MAX_COLORS)
-		return null;
-
-	return g_LDColors[colnum];
-}
-
-// =============================================================================
-// =============================================================================
-void setColor (int colnum, LDColor* col)
-{
-	if (colnum < 0 || colnum >= MAX_COLORS)
-		return;
-
-	g_LDColors[colnum] = col;
-}
-
-// =============================================================================
-// =============================================================================
-int luma (QColor& col)
-{
-	return (0.2126f * col.red()) +
-		   (0.7152f * col.green()) +
-		   (0.0722f * col.blue());
-}
--- a/src/Colors.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QColor>
-#include "Main.h"
-
-#define MAX_COLORS 512
-
-class LDColor
-{
-	public:
-		QString name, hexcode;
-		QColor faceColor, edgeColor;
-		int index;
-};
-
-void initColors();
-int luma (QColor& col);
-
-// Safely gets a color with the given number or null if no such color.
-LDColor* getColor (int colnum);
-void setColor (int colnum, LDColor* col);
-
-// Main and edge color identifiers
-static const int maincolor = 16;
-static const int edgecolor = 24;
--- a/src/Configuration.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,179 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  config.cxx: Configuration management. I don't like how unsafe QSettings
- *  is so this implements a type-safer and identifer-safer wrapping system of
- *  configuration variables. QSettings is used underlyingly, this is a matter
- *  of interface.
- */
-
-#include <errno.h>
-#include <QDir>
-#include <QTextStream>
-#include <QSettings>
-#include "Main.h"
-#include "Configuration.h"
-#include "Misc.h"
-#include "MainWindow.h"
-#include "Document.h"
-
-#ifdef _WIN32
-# define EXTENSION ".ini"
-#else
-# define EXTENSION ".cfg"
-#endif // _WIN32
-
-Config*							g_configPointers[MAX_CONFIG];
-static int						g_cfgPointerCursor = 0;
-static QMap<QString, Config*>	g_configsByName;
-static QList<Config*>			g_configs;
-
-// =============================================================================
-// Get the QSettings object.
-// =============================================================================
-static QSettings* getSettingsObject()
-{
-	QString path = qApp->applicationDirPath() + "/" UNIXNAME EXTENSION;
-	return new QSettings (path, QSettings::IniFormat);
-}
-
-Config::Config (QString name) :
-	m_name (name) {}
-
-// =============================================================================
-// Load the configuration from file
-// =============================================================================
-bool Config::load()
-{
-	QSettings* settings = getSettingsObject();
-	print ("config::load: Loading configuration file from %1\n", settings->fileName());
-
-	for (Config* cfg : g_configPointers)
-	{
-		if (!cfg)
-			break;
-
-		QVariant val = settings->value (cfg->name(), cfg->getDefaultAsVariant());
-		cfg->loadFromVariant (val);
-		g_configsByName[cfg->name()] = cfg;
-		g_configs << cfg;
-	}
-
-	settings->deleteLater();
-	return true;
-}
-
-// =============================================================================
-//
-// Save the configuration to disk
-//
-bool Config::save()
-{
-	QSettings* settings = getSettingsObject();
-
-	for (Config* cfg : g_configs)
-	{
-		if (!cfg->isDefault())
-			settings->setValue (cfg->name(), cfg->toVariant());
-		else
-			settings->remove (cfg->name());
-	}
-
-	settings->sync();
-	print ("Configuration saved to %1.\n", settings->fileName());
-	settings->deleteLater();
-	return true;
-}
-
-// =============================================================================
-// Reset configuration to defaults.
-// =============================================================================
-void Config::reset()
-{
-	for (Config* cfg : g_configs)
-		cfg->resetValue();
-}
-
-// =============================================================================
-// Where is the configuration file located at?
-// =============================================================================
-QString Config::filepath (QString file)
-{
-	return Config::dirpath() + DIRSLASH + file;
-}
-
-// =============================================================================
-// Directory of the configuration file.
-// =============================================================================
-QString Config::dirpath()
-{
-	QSettings* cfg = getSettingsObject();
-	return dirname (cfg->fileName());
-}
-
-// =============================================================================
-// We cannot just add config objects to a list or vector because that would rely
-// on the vector's c-tor being called before the configs' c-tors. With global
-// variables we cannot assume that, therefore we need to use a C-style array here.
-// =============================================================================
-void Config::addToArray (Config* ptr)
-{
-	if (g_cfgPointerCursor == 0)
-		memset (g_configPointers, 0, sizeof g_configPointers);
-
-	assert (g_cfgPointerCursor < MAX_CONFIG);
-	g_configPointers[g_cfgPointerCursor++] = ptr;
-}
-
-// =============================================================================
-// =============================================================================
-template<class T> T* getConfigByName (QString name, Config::Type type)
-{
-	auto it = g_configsByName.find (name);
-
-	if (it == g_configsByName.end())
-		return null;
-
-	Config* cfg = it.value();
-
-	if (cfg->getType() != type)
-	{
-		fprint (stderr, "type of %1 is %2, not %3\n", name, cfg->getType(), type);
-		abort();
-	}
-
-	return reinterpret_cast<T*> (cfg);
-}
-
-// =============================================================================
-// =============================================================================
-#undef IMPLEMENT_CONFIG
-
-#define IMPLEMENT_CONFIG(NAME)										\
-	NAME##Config* NAME##Config::getByName (QString name)			\
-	{																\
-		return getConfigByName<NAME##Config> (name, E##NAME##Type);	\
-	}
-
-IMPLEMENT_CONFIG (Int)
-IMPLEMENT_CONFIG (String)
-IMPLEMENT_CONFIG (Bool)
-IMPLEMENT_CONFIG (Float)
-IMPLEMENT_CONFIG (List)
-IMPLEMENT_CONFIG (KeySequence)
-IMPLEMENT_CONFIG (Vertex)
--- a/src/Configuration.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QString>
-#include <QVariant>
-#include <QKeySequence>
-#include "Macros.h"
-#include "Types.h"
-
-class QSettings;
-
-#define MAX_INI_LINE 512
-#define MAX_CONFIG 512
-
-#define cfg(T, NAME, DEFAULT) \
-	Config::T##Type NAME; \
-	T##Config config_##NAME (&NAME, #NAME, DEFAULT);
-
-#define extern_cfg(T, NAME) extern Config::T##Type NAME;
-
-// =========================================================
-class Config
-{
-	PROPERTY (private, QString, name, setName, STOCK_WRITE)
-
-	public:
-		enum Type
-		{
-			EIntType,
-			EStringType,
-			EFloatType,
-			EBoolType,
-			EKeySequenceType,
-			EListType,
-			EVertexType,
-		};
-
-		using IntType			= int;
-		using StringType		= QString;
-		using FloatType			= float;
-		using BoolType			= bool;
-		using KeySequenceType	= QKeySequence;
-		using ListType			= QList<QVariant>;
-		using VertexType		= Vertex;
-
-		Config (QString name);
-
-		virtual QVariant	getDefaultAsVariant() const = 0;
-		virtual Type		getType() const = 0;
-		virtual bool		isDefault() const = 0;
-		virtual void		loadFromVariant (const QVariant& val) = 0;
-		virtual void		resetValue() = 0;
-		virtual QVariant	toVariant() const = 0;
-
-		// ------------------------------------------
-		static bool load();
-		static bool save();
-		static void reset();
-		static QString dirpath();
-		static QString filepath (QString file);
-
-	protected:
-		static void addToArray (Config* ptr);
-};
-
-// =============================================================================
-#define IMPLEMENT_CONFIG(NAME)													\
-public:																			\
-	using ValueType = Config::NAME##Type;										\
-																				\
-	NAME##Config (ValueType* valueptr, QString name, ValueType def) :			\
-		Config (name),															\
-		m_valueptr (valueptr),													\
-		m_default (def)															\
-	{																			\
-		Config::addToArray (this);												\
-		*m_valueptr = def;														\
-	}																			\
-																				\
-	inline ValueType getValue() const											\
-	{																			\
-		return *m_valueptr;														\
-	}																			\
-																				\
-	inline void setValue (ValueType val)										\
-	{																			\
-		*m_valueptr = val;														\
-	}																			\
-																				\
-	virtual Config::Type getType() const										\
-	{																			\
-		return Config::E##NAME##Type;											\
-	}																			\
-																				\
-	virtual void resetValue()													\
-	{																			\
-		*m_valueptr = m_default;												\
-	}																			\
-																				\
-	virtual const ValueType& getDefault() const									\
-	{																			\
-		return m_default;														\
-	}																			\
-																				\
-	virtual bool isDefault() const												\
-	{																			\
-		return *m_valueptr == m_default;										\
-	}																			\
-																				\
-	virtual void loadFromVariant (const QVariant& val)							\
-	{																			\
-		*m_valueptr = val.value<ValueType>();									\
-	}																			\
-																				\
-	virtual QVariant toVariant() const											\
-	{																			\
-		return QVariant::fromValue<ValueType> (*m_valueptr);					\
-	}																			\
-																				\
-	virtual QVariant getDefaultAsVariant() const								\
-	{																			\
-		return QVariant::fromValue<ValueType> (m_default);						\
-	}																			\
-																				\
-	static NAME##Config* getByName (QString name);								\
-																				\
-private:																		\
-	ValueType*	m_valueptr;														\
-	ValueType	m_default;
-
-// =============================================================================
-//
-class IntConfig : public Config
-{
-	IMPLEMENT_CONFIG (Int)
-};
-
-// =============================================================================
-//
-class StringConfig : public Config
-{
-	IMPLEMENT_CONFIG (String)
-};
-
-// =============================================================================
-//
-class FloatConfig : public Config
-{
-	IMPLEMENT_CONFIG (Float)
-};
-
-// =============================================================================
-//
-class BoolConfig : public Config
-{
-	IMPLEMENT_CONFIG (Bool)
-};
-
-// =============================================================================
-//
-class KeySequenceConfig : public Config
-{
-	IMPLEMENT_CONFIG (KeySequence)
-};
-
-// =============================================================================
-//
-class ListConfig : public Config
-{
-	IMPLEMENT_CONFIG (List)
-};
-
-// =============================================================================
-//
-class VertexConfig : public Config
-{
-	IMPLEMENT_CONFIG (Vertex)
-};
--- a/src/ConfigurationDialog.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,803 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  configDialog.cxx: Settings dialog and everything related to it.
- *  Actual configuration core is in config.cxx.
- */
-
-#include <QGridLayout>
-#include <QFileDialog>
-#include <QColorDialog>
-#include <QBoxLayout>
-#include <QKeyEvent>
-#include <QGroupBox>
-#include <QDoubleSpinBox>
-#include <QLineEdit>
-#include <QCheckBox>
-#include "Main.h"
-#include "ConfigurationDialog.h"
-#include "Document.h"
-#include "Configuration.h"
-#include "Misc.h"
-#include "Colors.h"
-#include "ColorSelector.h"
-#include "GLRenderer.h"
-#include "ui_config.h"
-
-extern_cfg (String, gl_bgcolor);
-extern_cfg (String, gl_maincolor);
-extern_cfg (Bool, lv_colorize);
-extern_cfg (Bool, gl_colorbfc);
-extern_cfg (Float, gl_maincolor_alpha);
-extern_cfg (Int, gl_linethickness);
-extern_cfg (String, gui_colortoolbar);
-extern_cfg (Bool, edit_schemanticinline);
-extern_cfg (Bool, gl_blackedges);
-extern_cfg (Bool, gl_aa);
-extern_cfg (Bool, gui_implicitfiles);
-extern_cfg (String, net_downloadpath);
-extern_cfg (Bool, net_guesspaths);
-extern_cfg (Bool, net_autoclose);
-extern_cfg (Bool, gl_logostuds);
-extern_cfg (Bool,	gl_linelengths);
-extern_cfg (String, ld_defaultname);
-extern_cfg (String, ld_defaultuser);
-extern_cfg (Int, ld_defaultlicense);
-extern_cfg (String, gl_selectcolor);
-extern_cfg (String, prog_ytruder);
-extern_cfg (String, prog_rectifier);
-extern_cfg (String, prog_intersector);
-extern_cfg (String, prog_coverer);
-extern_cfg (String, prog_isecalc);
-extern_cfg (String, prog_edger2);
-extern_cfg (Bool, prog_ytruder_wine);
-extern_cfg (Bool, prog_rectifier_wine);
-extern_cfg (Bool, prog_intersector_wine);
-extern_cfg (Bool, prog_coverer_wine);
-extern_cfg (Bool, prog_isecalc_wine);
-extern_cfg (Bool, prog_edger2_wine);
-
-const char* g_extProgPathFilter =
-#ifdef _WIN32
-	"Applications (*.exe)(*.exe);;All files (*.*)(*.*)";
-#else
-	"";
-#endif
-
-// =============================================================================
-// =============================================================================
-ConfigDialog::ConfigDialog (ConfigDialog::Tab deftab, QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f)
-{
-	assert (g_win != null);
-	ui = new Ui_ConfigUI;
-	ui->setupUi (this);
-
-	// Interface tab
-	setButtonBackground (ui->backgroundColorButton, gl_bgcolor);
-	connect (ui->backgroundColorButton, SIGNAL (clicked()),
-			 this, SLOT (slot_setGLBackground()));
-
-	setButtonBackground (ui->mainColorButton, gl_maincolor);
-	connect (ui->mainColorButton, SIGNAL (clicked()),
-			 this, SLOT (slot_setGLForeground()));
-
-	setButtonBackground (ui->selColorButton, gl_selectcolor);
-	connect (ui->selColorButton, SIGNAL (clicked()),
-			 this, SLOT (slot_setGLSelectColor()));
-
-	ui->mainColorAlpha->setValue (gl_maincolor_alpha * 10.0f);
-	ui->lineThickness->setValue (gl_linethickness);
-	ui->colorizeObjects->setChecked (lv_colorize);
-	ui->colorBFC->setChecked (gl_colorbfc);
-	ui->blackEdges->setChecked (gl_blackedges);
-	ui->m_aa->setChecked (gl_aa);
-	ui->implicitFiles->setChecked (gui_implicitfiles);
-	ui->m_logostuds->setChecked (gl_logostuds);
-	ui->linelengths->setChecked (gl_linelengths);
-
-	int i = 0;
-
-	for (QAction* act : g_win->findChildren<QAction*>())
-	{
-		KeySequenceConfig* cfg = g_win->shortcutForAction (act);
-
-		if (cfg)
-			addShortcut (*cfg, act, i);
-	}
-
-	ui->shortcutsList->setSortingEnabled (true);
-	ui->shortcutsList->sortItems();
-
-	connect (ui->shortcut_set, SIGNAL (clicked()), this, SLOT (slot_setShortcut()));
-	connect (ui->shortcut_reset, SIGNAL (clicked()), this, SLOT (slot_resetShortcut()));
-	connect (ui->shortcut_clear, SIGNAL (clicked()), this, SLOT (slot_clearShortcut()));
-
-	quickColors = quickColorsFromConfig();
-	updateQuickColorList();
-
-	connect (ui->quickColor_add, SIGNAL (clicked()), this, SLOT (slot_setColor()));
-	connect (ui->quickColor_remove, SIGNAL (clicked()), this, SLOT (slot_delColor()));
-	connect (ui->quickColor_edit, SIGNAL (clicked()), this, SLOT (slot_setColor()));
-	connect (ui->quickColor_addSep, SIGNAL (clicked()), this, SLOT (slot_addColorSeparator()));
-	connect (ui->quickColor_moveUp, SIGNAL (clicked()), this, SLOT (slot_moveColor()));
-	connect (ui->quickColor_moveDown, SIGNAL (clicked()), this, SLOT (slot_moveColor()));
-	connect (ui->quickColor_clear, SIGNAL (clicked()), this, SLOT (slot_clearColors()));
-
-	ui->downloadPath->setText (net_downloadpath);
-	ui->guessNetPaths->setChecked (net_guesspaths);
-	ui->autoCloseNetPrompt->setChecked (net_autoclose);
-	connect (ui->findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder()));
-
-	ui->m_profileName->setText (ld_defaultname);
-	ui->m_profileUsername->setText (ld_defaultuser);
-	ui->m_profileLicense->setCurrentIndex (ld_defaultlicense);
-
-	initGrids();
-	initExtProgs();
-	selectPage (deftab);
-
-	connect (ui->buttonBox, SIGNAL (clicked (QAbstractButton*)),
-		this, SLOT (buttonClicked (QAbstractButton*)));
-
-	connect (ui->m_pages, SIGNAL (currentChanged (int)),
-		this, SLOT (selectPage (int)));
-
-	connect (ui->m_pagelist, SIGNAL (currentRowChanged (int)),
-		this, SLOT (selectPage (int)));
-}
-
-// =============================================================================
-// =============================================================================
-ConfigDialog::~ConfigDialog()
-{
-	delete ui;
-}
-
-// =============================================================================
-// =============================================================================
-void ConfigDialog::selectPage (int row)
-{
-	ui->m_pagelist->setCurrentRow (row);
-	ui->m_pages->setCurrentIndex (row);
-}
-
-// =============================================================================
-// Adds a shortcut entry to the list of shortcuts.
-// =============================================================================
-void ConfigDialog::addShortcut (KeySequenceConfig& cfg, QAction* act, int& i)
-{
-	ShortcutListItem* item = new ShortcutListItem;
-	item->setIcon (act->icon());
-	item->setKeyConfig (&cfg);
-	item->setAction (act);
-	setShortcutText (item);
-
-	// If the action doesn't have a valid icon, use an empty one
-	// so that the list is kept aligned.
-	if (act->icon().isNull())
-		item->setIcon (getIcon ("empty"));
-
-	ui->shortcutsList->insertItem (i++, item);
-}
-
-// =============================================================================
-// Initializes the table of grid stuff
-// =============================================================================
-void ConfigDialog::initGrids()
-{
-	QGridLayout* gridlayout = new QGridLayout;
-	QLabel* xlabel = new QLabel ("X"),
-	*ylabel = new QLabel ("Y"),
-	*zlabel = new QLabel ("Z"),
-	*anglabel = new QLabel ("Angle");
-	int i = 1;
-
-	for (QLabel* label : QList<QLabel*> ({xlabel, ylabel, zlabel, anglabel}))
-	{
-		label->setAlignment (Qt::AlignCenter);
-		gridlayout->addWidget (label, 0, i++);
-	}
-
-	for (int i = 0; i < g_NumGrids; ++i)
-	{
-		// Icon
-		lb_gridIcons[i] = new QLabel;
-		lb_gridIcons[i]->setPixmap (getIcon (format ("grid-%1", QString (g_GridInfo[i].name).toLower())));
-
-		// Text label
-		lb_gridLabels[i] = new QLabel (format ("%1:", g_GridInfo[i].name));
-
-		QHBoxLayout* labellayout = new QHBoxLayout;
-		labellayout->addWidget (lb_gridIcons[i]);
-		labellayout->addWidget (lb_gridLabels[i]);
-		gridlayout->addLayout (labellayout, i + 1, 0);
-
-		// Add the widgets
-		for (int j = 0; j < 4; ++j)
-		{
-			dsb_gridData[i][j] = new QDoubleSpinBox;
-
-			// Set the maximum angle
-			if (j == 3)
-				dsb_gridData[i][j]->setMaximum (360);
-
-			dsb_gridData[i][j]->setValue (*g_GridInfo[i].confs[j]);
-			gridlayout->addWidget (dsb_gridData[i][j], i + 1, j + 1);
-		}
-	}
-
-	ui->grids->setLayout (gridlayout);
-}
-
-// =============================================================================
-// =============================================================================
-static struct LDExtProgInfo
-{
-	const QString		name,
-						iconname;
-	QString* const		path;
-	QLineEdit*		input;
-	QPushButton*	setPathButton;
-#ifndef _WIN32
-	bool* const		wine;
-	QCheckBox*		wineBox;
-#endif // _WIN32
-} g_LDExtProgInfo[] =
-{
-#ifndef _WIN32
-# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &prog_##LOWNAME, null, null, &prog_##LOWNAME##_wine, null },
-#else
-# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &prog_##LOWNAME, null, null },
-#endif
-	EXTPROG (Ytruder, ytruder)
-	EXTPROG (Rectifier, rectifier)
-	EXTPROG (Intersector, intersector)
-	EXTPROG (Isecalc, isecalc)
-	EXTPROG (Coverer, coverer)
-	EXTPROG (Edger2, edger2)
-#undef EXTPROG
-};
-
-// =============================================================================
-// Initializes the stuff in the ext programs tab
-// =============================================================================
-void ConfigDialog::initExtProgs()
-{
-	QGridLayout* pathsLayout = new QGridLayout;
-	int row = 0;
-
-	for (LDExtProgInfo& info : g_LDExtProgInfo)
-	{
-		QLabel* icon = new QLabel,
-		*progLabel = new QLabel (info.name);
-		QLineEdit* input = new QLineEdit;
-		QPushButton* setPathButton = new QPushButton;
-
-		icon->setPixmap (getIcon (info.iconname));
-		input->setText (*info.path);
-		setPathButton->setIcon (getIcon ("folder"));
-		info.input = input;
-		info.setPathButton = setPathButton;
-
-		connect (setPathButton, SIGNAL (clicked()), this, SLOT (slot_setExtProgPath()));
-
-		pathsLayout->addWidget (icon, row, 0);
-		pathsLayout->addWidget (progLabel, row, 1);
-		pathsLayout->addWidget (input, row, 2);
-		pathsLayout->addWidget (setPathButton, row, 3);
-
-#ifndef _WIN32
-		QCheckBox* wineBox = new QCheckBox ("Wine");
-		wineBox->setChecked (*info.wine);
-		info.wineBox = wineBox;
-		pathsLayout->addWidget (wineBox, row, 4);
-#endif
-
-		++row;
-	}
-
-	ui->extProgs->setLayout (pathsLayout);
-}
-
-// =============================================================================
-// Set the settings based on widget data.
-// =============================================================================
-void ConfigDialog::applySettings()
-{
-	// Apply configuration
-	lv_colorize = ui->colorizeObjects->isChecked();
-	gl_colorbfc = ui->colorBFC->isChecked();
-	gl_blackedges = ui->blackEdges->isChecked();
-	gl_maincolor_alpha = ( (double) ui->mainColorAlpha->value()) / 10.0f;
-	gl_linethickness = ui->lineThickness->value();
-	gui_implicitfiles = ui->implicitFiles->isChecked();
-	net_downloadpath = ui->downloadPath->text();
-	net_guesspaths = ui->guessNetPaths->isChecked();
-	net_autoclose = ui->autoCloseNetPrompt->isChecked();
-	gl_logostuds = ui->m_logostuds->isChecked();
-	gl_linelengths = ui->linelengths->isChecked();
-	ld_defaultuser = ui->m_profileUsername->text();
-	ld_defaultname = ui->m_profileName->text();
-	ld_defaultlicense = ui->m_profileLicense->currentIndex();
-	gl_aa = ui->m_aa->isChecked();
-
-	// Rebuild the quick color toolbar
-	g_win->setQuickColors (quickColors);
-	gui_colortoolbar = quickColorString();
-
-	// Set the grid settings
-	for (int i = 0; i < g_NumGrids; ++i)
-		for (int j = 0; j < 4; ++j)
-			*g_GridInfo[i].confs[j] = dsb_gridData[i][j]->value();
-
-	// Apply key shortcuts
-	g_win->updateActionShortcuts();
-
-	// Ext program settings
-	for (const LDExtProgInfo& info : g_LDExtProgInfo)
-	{
-		*info.path = info.input->text();
-
-#ifndef _WIN32
-		*info.wine = info.wineBox->isChecked();
-#endif // _WIN32
-	}
-
-	Config::save();
-	reloadAllSubfiles();
-	loadLogoedStuds();
-	g_win->R()->setBackground();
-	g_win->doFullRefresh();
-	g_win->updateDocumentList();
-}
-
-// =============================================================================
-// A dialog button was clicked
-// =============================================================================
-void ConfigDialog::buttonClicked (QAbstractButton* button)
-{
-	typedef QDialogButtonBox QDDB;
-	QDialogButtonBox* dbb = ui->buttonBox;
-
-	if (button == dbb->button (QDDB::Ok))
-	{
-		applySettings();
-		accept();
-	} elif (button == dbb->button (QDDB::Apply))
-	{
-		applySettings();
-	} elif (button == dbb->button (QDDB::Cancel))
-	{
-		reject();
-	}
-}
-
-// =============================================================================
-// Update the list of color toolbar items in the quick color tab.
-// =============================================================================
-void ConfigDialog::updateQuickColorList (LDQuickColor* sel)
-{
-	for (QListWidgetItem * item : quickColorItems)
-		delete item;
-
-	quickColorItems.clear();
-
-	// Init table items
-	for (LDQuickColor& entry : quickColors)
-	{
-		QListWidgetItem* item = new QListWidgetItem;
-
-		if (entry.isSeparator())
-		{
-			item->setText ("--------");
-			item->setIcon (getIcon ("empty"));
-		}
-		else
-		{
-			LDColor* col = entry.color();
-
-			if (col == null)
-			{
-				item->setText ("[[unknown color]]");
-				item->setIcon (getIcon ("error"));
-			}
-			else
-			{
-				item->setText (col->name);
-				item->setIcon (makeColorIcon (col, 16));
-			}
-		}
-
-		ui->quickColorList->addItem (item);
-		quickColorItems << item;
-
-		if (sel && &entry == sel)
-		{
-			ui->quickColorList->setCurrentItem (item);
-			ui->quickColorList->scrollToItem (item);
-		}
-	}
-}
-
-// =============================================================================
-// Quick colors: add or edit button was clicked.
-// =============================================================================
-void ConfigDialog::slot_setColor()
-{
-	LDQuickColor* entry = null;
-	QListWidgetItem* item = null;
-	const bool isNew = static_cast<QPushButton*> (sender()) == ui->quickColor_add;
-
-	if (isNew == false)
-	{
-		item = getSelectedQuickColor();
-
-		if (!item)
-			return;
-
-		int i = getItemRow (item, quickColorItems);
-		entry = &quickColors[i];
-
-		if (entry->isSeparator() == true)
-			return; // don't color separators
-	}
-
-	int defval = entry ? entry->color()->index : -1;
-	int val;
-
-	if (ColorSelector::selectColor (val, defval, this) == false)
-		return;
-
-	if (entry)
-		entry->setColor (getColor (val));
-	else
-	{
-		LDQuickColor entry (getColor (val), null);
-
-		item = getSelectedQuickColor();
-		int idx = (item) ? getItemRow (item, quickColorItems) + 1 : quickColorItems.size();
-
-		quickColors.insert (idx, entry);
-		entry = quickColors[idx];
-	}
-
-	updateQuickColorList (entry);
-}
-
-// =============================================================================
-// Remove a quick color
-// =============================================================================
-void ConfigDialog::slot_delColor()
-{
-	if (ui->quickColorList->selectedItems().isEmpty())
-		return;
-
-	QListWidgetItem* item = ui->quickColorList->selectedItems() [0];
-	quickColors.removeAt (getItemRow (item, quickColorItems));
-	updateQuickColorList();
-}
-
-// =============================================================================
-// Move a quick color up/down
-// =============================================================================
-void ConfigDialog::slot_moveColor()
-{
-	const bool up = (static_cast<QPushButton*> (sender()) == ui->quickColor_moveUp);
-
-	if (ui->quickColorList->selectedItems().isEmpty())
-		return;
-
-	QListWidgetItem* item = ui->quickColorList->selectedItems() [0];
-	int idx = getItemRow (item, quickColorItems);
-	int dest = up ? (idx - 1) : (idx + 1);
-
-	if (dest < 0 || dest >= quickColorItems.size())
-		return; // destination out of bounds
-
-	LDQuickColor tmp = quickColors[dest];
-	quickColors[dest] = quickColors[idx];
-	quickColors[idx] = tmp;
-
-	updateQuickColorList (&quickColors[dest]);
-}
-
-// =============================================================================
-//
-// Add a separator to quick colors
-//
-void ConfigDialog::slot_addColorSeparator()
-{
-	quickColors << LDQuickColor::getSeparator();
-	updateQuickColorList (&quickColors[quickColors.size() - 1]);
-}
-
-// =============================================================================
-//
-// Clear all quick colors
-//
-void ConfigDialog::slot_clearColors()
-{
-	quickColors.clear();
-	updateQuickColorList();
-}
-
-// =============================================================================
-//
-// Pick a color and set the appropriate configuration option.
-//
-void ConfigDialog::pickColor (QString& conf, QPushButton* button)
-{
-	QColor col = QColorDialog::getColor (QColor (conf));
-
-	if (col.isValid())
-	{
-		int r = col.red(),
-			g = col.green(),
-			b = col.blue();
-
-		QString colname;
-		colname.sprintf ("#%.2X%.2X%.2X", r, g, b);
-		conf = colname;
-		setButtonBackground (button, colname);
-	}
-}
-
-// =============================================================================
-// =============================================================================
-void ConfigDialog::slot_setGLBackground()
-{
-	pickColor (gl_bgcolor, ui->backgroundColorButton);
-}
-
-// =============================================================================
-// =============================================================================
-void ConfigDialog::slot_setGLForeground()
-{
-	pickColor (gl_maincolor, ui->mainColorButton);
-}
-
-// =============================================================================
-// =============================================================================
-void ConfigDialog::slot_setGLSelectColor()
-{
-	pickColor (gl_selectcolor, ui->selColorButton);
-}
-
-// =============================================================================
-// Sets background color of a given button.
-// =============================================================================
-void ConfigDialog::setButtonBackground (QPushButton* button, QString value)
-{
-	button->setIcon (getIcon ("colorselect"));
-	button->setAutoFillBackground (true);
-	button->setStyleSheet (format ("background-color: %1", value));
-}
-
-// =============================================================================
-// Finds the given list widget item in the list of widget items given.
-// =============================================================================
-int ConfigDialog::getItemRow (QListWidgetItem* item, QList<QListWidgetItem*>& haystack)
-{
-	int i = 0;
-
-	for (QListWidgetItem* it : haystack)
-	{
-		if (it == item)
-			return i;
-
-		++i;
-	}
-
-	return -1;
-}
-
-// =============================================================================
-// Which quick color is currently selected?
-// =============================================================================
-QListWidgetItem* ConfigDialog::getSelectedQuickColor()
-{
-	if (ui->quickColorList->selectedItems().isEmpty())
-		return null;
-
-	return ui->quickColorList->selectedItems() [0];
-}
-
-// =============================================================================
-// Get the list of shortcuts selected
-// =============================================================================
-QList<ShortcutListItem*> ConfigDialog::getShortcutSelection()
-{
-	QList<ShortcutListItem*> out;
-
-	for (QListWidgetItem* entry : ui->shortcutsList->selectedItems())
-		out << static_cast<ShortcutListItem*> (entry);
-
-	return out;
-}
-
-// =============================================================================
-// Edit the shortcut of a given action.
-// =============================================================================
-void ConfigDialog::slot_setShortcut()
-{
-	QList<ShortcutListItem*> sel = getShortcutSelection();
-
-	if (sel.size() < 1)
-		return;
-
-	ShortcutListItem* item = sel[0];
-
-	if (KeySequenceDialog::staticDialog (item->keyConfig(), this))
-		setShortcutText (item);
-}
-
-// =============================================================================
-// Reset a shortcut to defaults
-// =============================================================================
-void ConfigDialog::slot_resetShortcut()
-{
-	QList<ShortcutListItem*> sel = getShortcutSelection();
-
-	for (ShortcutListItem* item : sel)
-	{
-		item->keyConfig()->reset();
-		setShortcutText (item);
-	}
-}
-
-// =============================================================================
-// Remove the shortcut of an action.
-// =============================================================================
-void ConfigDialog::slot_clearShortcut()
-{
-	QList<ShortcutListItem*> sel = getShortcutSelection();
-
-	for (ShortcutListItem* item : sel)
-	{
-		item->keyConfig()->setValue (QKeySequence());
-		setShortcutText (item);
-	}
-}
-
-// =============================================================================
-// Set the path of an external program
-// =============================================================================
-void ConfigDialog::slot_setExtProgPath()
-{
-	const LDExtProgInfo* info = null;
-
-	for (const LDExtProgInfo& it : g_LDExtProgInfo)
-	{
-		if (it.setPathButton == sender())
-		{
-			info = &it;
-			break;
-		}
-	}
-
-	assert (info != null);
-	QString fpath = QFileDialog::getOpenFileName (this, format ("Path to %1", info->name), *info->path, g_extProgPathFilter);
-
-	if (fpath.isEmpty())
-		return;
-
-	info->input->setText (fpath);
-}
-
-// =============================================================================
-//
-// '...' button pressed for the download path
-//
-void ConfigDialog::slot_findDownloadFolder()
-{
-	QString dpath = QFileDialog::getExistingDirectory();
-	ui->downloadPath->setText (dpath);
-}
-
-// =============================================================================
-//
-// Updates the text string for a given shortcut list item
-//
-void ConfigDialog::setShortcutText (ShortcutListItem* item)
-{
-	QAction* act = item->action();
-	QString label = act->iconText();
-	QString keybind = item->keyConfig()->getValue().toString();
-	item->setText (format ("%1 (%2)", label, keybind));
-}
-
-// =============================================================================
-// Gets the configuration string of the quick color toolbar
-// =============================================================================
-QString ConfigDialog::quickColorString()
-{
-	QString val;
-
-	for (const LDQuickColor& entry : quickColors)
-	{
-		if (val.length() > 0)
-			val += ':';
-
-		if (entry.isSeparator())
-			val += '|';
-		else
-			val += format ("%1", entry.color()->index);
-	}
-
-	return val;
-}
-
-// ===============================================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// ===============================================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// ===============================================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// ===============================================================================================
-KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f), seq (seq)
-{
-	lb_output = new QLabel;
-	IMPLEMENT_DIALOG_BUTTONS
-
-	setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a "
-		"shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to "
-		"dismiss."));
-
-	QVBoxLayout* layout = new QVBoxLayout;
-	layout->addWidget (lb_output);
-	layout->addWidget (bbx_buttons);
-	setLayout (layout);
-
-	updateOutput();
-}
-
-// =============================================================================
-// =============================================================================
-bool KeySequenceDialog::staticDialog (KeySequenceConfig* cfg, QWidget* parent)
-{
-	KeySequenceDialog dlg (cfg->getValue(), parent);
-
-	if (dlg.exec() == false)
-		return false;
-
-	cfg->setValue (dlg.seq);
-	return true;
-}
-
-// =============================================================================
-// =============================================================================
-void KeySequenceDialog::updateOutput()
-{
-	QString shortcut = seq.toString();
-
-	if (seq == QKeySequence())
-		shortcut = "&lt;empty&gt;";
-
-	QString text = format ("<center><b>%1</b></center>", shortcut);
-	lb_output->setText (text);
-}
-
-// =============================================================================
-// =============================================================================
-void KeySequenceDialog::keyPressEvent (QKeyEvent* ev)
-{
-	seq = ev->key() + ev->modifiers();
-	updateOutput();
-}
--- a/src/ConfigurationDialog.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include "MainWindow.h"
-#include <QDialog>
-
-class Ui_ConfigUI;
-class QLabel;
-class QDoubleSpinBox;
-
-// =============================================================================
-class ShortcutListItem : public QListWidgetItem
-{
-	PROPERTY (public,	KeySequenceConfig*,	keyConfig,	setKeyConfig,	STOCK_WRITE)
-	PROPERTY (public,	QAction*,			action,		setAction,		STOCK_WRITE)
-
-	public:
-		explicit ShortcutListItem (QListWidget* view = null, int type = Type) :
-			QListWidgetItem (view, type) {}
-};
-
-// =============================================================================
-class ConfigDialog : public QDialog
-{
-	Q_OBJECT
-
-	public:
-		enum Tab
-		{
-			InterfaceTab,
-			ProfileTab,
-			ShortcutsTab,
-			QuickColorsTab,
-			GridsTab,
-			ExtProgsTab,
-			DownloadTab
-		};
-
-		explicit ConfigDialog (Tab deftab = InterfaceTab, QWidget* parent = null, Qt::WindowFlags f = 0);
-		virtual ~ConfigDialog();
-		float getGridValue (int i, int j) const;
-
-		QList<LDQuickColor> quickColors;
-		QDoubleSpinBox* dsb_gridData[3][4];
-
-	private:
-		Ui_ConfigUI* ui;
-		QLabel* lb_gridLabels[3];
-		QLabel* lb_gridIcons[3];
-		QList<QListWidgetItem*> quickColorItems;
-
-		void applySettings();
-		void addShortcut (KeySequenceConfig& cfg, QAction* act, int& i);
-		void setButtonBackground (QPushButton* button, QString value);
-		void pickColor (QString& conf, QPushButton* button);
-		void updateQuickColorList (LDQuickColor* sel = null);
-		void setShortcutText (ShortcutListItem* item);
-		int getItemRow (QListWidgetItem* item, QList<QListWidgetItem*>& haystack);
-		QString quickColorString();
-		QListWidgetItem* getSelectedQuickColor();
-		QList<ShortcutListItem*> getShortcutSelection();
-		void initGrids();
-		void initExtProgs();
-
-	private slots:
-		void slot_setGLBackground();
-		void slot_setGLForeground();
-		void slot_setGLSelectColor();
-		void slot_setShortcut();
-		void slot_resetShortcut();
-		void slot_clearShortcut();
-		void slot_setColor();
-		void slot_delColor();
-		void slot_addColorSeparator();
-		void slot_moveColor();
-		void slot_clearColors();
-		void slot_setExtProgPath();
-		void slot_findDownloadFolder();
-		void buttonClicked (QAbstractButton* button);
-		void selectPage (int row);
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class KeySequenceDialog : public QDialog
-{
-	Q_OBJECT
-
-	public:
-		explicit KeySequenceDialog (QKeySequence seq, QWidget* parent = null, Qt::WindowFlags f = 0);
-		static bool staticDialog (KeySequenceConfig* cfg, QWidget* parent = null);
-
-		QLabel* lb_output;
-		QDialogButtonBox* bbx_buttons;
-		QKeySequence seq;
-
-	private:
-		void updateOutput();
-
-	private slots:
-		virtual void keyPressEvent (QKeyEvent* ev) override;
-};
--- a/src/CrashCatcher.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifdef __unix__
-
-#include <QString>
-#include <QProcess>
-#include <QTemporaryFile>
-#include <QMessageBox>
-#include <unistd.h>
-#include <signal.h>
-
-#ifdef Q_OS_LINUX
-# include <sys/prctl.h>
-#endif
-
-#include "CrashCatcher.h"
-#include "Types.h"
-#include "Dialogs.h"
-
-// Is the crash catcher active now?
-static bool g_crashCatcherActive = false;
-
-// If an assertion failed, what was it?
-static QString g_assertionFailure;
-
-// List of signals to catch and crash on
-static QList<int> g_signalsToCatch ({
-	SIGSEGV, // segmentation fault
-	SIGABRT, // abort() calls
-	SIGFPE, // floating point exceptions (e.g. division by zero)
-	SIGILL, // illegal instructions
-});
-
-// =============================================================================
-//
-static void handleCrash (int sig)
-{
-	printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig);
-
-	if (g_crashCatcherActive)
-	{
-		printf ("caught signal while crash catcher is active!\n");
-		exit (149);
-	}
-
-	const pid_t pid = getpid();
-	QProcess proc;
-	QTemporaryFile commandsFile;
-
-	g_crashCatcherActive = true;
-
-	if (commandsFile.open())
-	{
-		commandsFile.write (format ("attach %1\n", pid).toLocal8Bit());
-		commandsFile.write (QString ("backtrace full\n").toLocal8Bit());
-		commandsFile.write (QString ("detach\n").toLocal8Bit());
-		commandsFile.write (QString ("quit").toLocal8Bit());
-		commandsFile.flush();
-		commandsFile.close();
-	}
-
-	QStringList args ({"-x", commandsFile.fileName()});
-
-	proc.start ("gdb", args);
-
-	// Linux doesn't allow ptrace to be used on anything but direct child processes
-	// so we need to use prctl to register an exception to this to allow GDB attach to us.
-	// We need to do this now and no earlier because only now we actually know GDB's PID.
-#ifdef Q_OS_LINUX
-	prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0);
-#endif
-
-	proc.waitForFinished (1000);
-	QString output = QString (proc.readAllStandardOutput());
-	QString err = QString (proc.readAllStandardError());
-
-	bombBox (format ("<h3>Program crashed with signal %1</h3>\n\n"
-		"%2"
-		"<p><b>GDB <tt>stdout</tt>:</b></p><pre>%3</pre>\n"
-		"<p><b>GDB <tt>stderr</tt>:</b></p><pre>%4</pre>",
-		sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err));
-}
-
-// =============================================================================
-//
-void initCrashCatcher()
-{
-	struct sigaction sighandler;
-	sighandler.sa_handler = &handleCrash;
-	sighandler.sa_flags = 0;
-	sigemptyset (&sighandler.sa_mask);
-
-	for (int sig : g_signalsToCatch)
-		sigaction (sig, &sighandler, null);
-
-	print ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch);
-}
-#endif // #ifdef __unix__
-
-// =============================================================================
-//
-// This function must be readily available in both Windows and Linux. We display
-// the bomb box straight in Windows while in Linux we let abort() trigger the
-// signal handler, which will cause the usual bomb box with GDB diagnostics.
-// Said prompt will embed the assertion failure information.
-//
-void assertionFailure (const char* file, int line, const char* funcname, const char* expr)
-{
-	QString errmsg = format (
-		"<p><b>File</b>: <tt>%1</tt><br />"
-		"<b>Line</b>: <tt>%2</tt><br />"
-		"<b>Function:</b> <tt>%3</tt></p>"
-		"<p>Assertion <b><tt>`%4'</tt></b> failed.</p>",
-		file, line, funcname, expr);
-
-	g_assertionFailure = errmsg;
-
-#ifndef __unix__
-	bombBox (errmsg);
-#endif
-
-	abort();
-}
\ No newline at end of file
--- a/src/CrashCatcher.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#ifdef __unix__
-void initCrashCatcher();
-#else // ifdef __unix__
-# define initCrashCatcher()
-#endif // ifdef __unix__
--- a/src/Dialogs.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,378 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDialog>
-#include <QLineEdit>
-#include <QSpinBox>
-#include <QDialogButtonBox>
-#include <QFileDialog>
-#include <QLabel>
-#include <QPushButton>
-#include <QBoxLayout>
-#include <QGridLayout>
-#include <QProgressBar>
-#include <QCheckBox>
-#include <QDesktopServices>
-#include <QMessageBox>
-#include <QUrl>
-#include "Dialogs.h"
-#include "Widgets.h"
-#include "MainWindow.h"
-#include "GLRenderer.h"
-#include "Documentation.h"
-#include "Document.h"
-#include "Dialogs.h"
-#include "ui_overlay.h"
-#include "ui_ldrawpath.h"
-#include "ui_openprogress.h"
-#include "ui_extprogpath.h"
-#include "ui_about.h"
-#include "ui_bombbox.h"
-
-extern const char* g_extProgPathFilter;
-extern_cfg (String, io_ldpath);
-
-// =============================================================================
-// =============================================================================
-OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f)
-{
-	ui = new Ui_OverlayUI;
-	ui->setupUi (this);
-
-	m_cameraArgs =
-	{
-		{ ui->top,    GL::ETopCamera },
-		{ ui->bottom, GL::EBottomCamera },
-		{ ui->front,  GL::EFrontCamera },
-		{ ui->back,   GL::EBackCamera },
-		{ ui->left,   GL::ELeftCamera },
-		{ ui->right,  GL::ERightCamera }
-	};
-
-	GL::EFixedCamera cam = g_win->R()->camera();
-
-	if (cam == GL::EFreeCamera)
-		cam = GL::ETopCamera;
-
-	connect (ui->width, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged()));
-	connect (ui->height, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged()));
-	connect (ui->buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_help()));
-	connect (ui->fileSearchButton, SIGNAL (clicked (bool)), this, SLOT (slot_fpath()));
-
-	slot_dimensionsChanged();
-	fillDefaults (cam);
-}
-
-// =============================================================================
-// =============================================================================
-OverlayDialog::~OverlayDialog()
-{
-	delete ui;
-}
-
-// =============================================================================
-// =============================================================================
-void OverlayDialog::fillDefaults (int newcam)
-{
-	LDGLOverlay& info = g_win->R()->getOverlay (newcam);
-	radioDefault<int> (newcam, m_cameraArgs);
-
-	if (info.img != null)
-	{
-		ui->filename->setText (info.fname);
-		ui->originX->setValue (info.ox);
-		ui->originY->setValue (info.oy);
-		ui->width->setValue (info.lw);
-		ui->height->setValue (info.lh);
-	}
-	else
-	{
-		ui->filename->setText ("");
-		ui->originX->setValue (0);
-		ui->originY->setValue (0);
-		ui->width->setValue (0.0f);
-		ui->height->setValue (0.0f);
-	}
-}
-
-// =============================================================================
-// =============================================================================
-QString OverlayDialog::fpath() const
-{
-	return ui->filename->text();
-}
-
-int OverlayDialog::ofsx() const
-{
-	return ui->originX->value();
-}
-
-int OverlayDialog::ofsy() const
-{
-	return ui->originY->value();
-}
-
-double OverlayDialog::lwidth() const
-{
-	return ui->width->value();
-}
-
-double OverlayDialog::lheight() const
-{
-	return ui->height->value();
-}
-
-int OverlayDialog::camera() const
-{
-	return radioSwitch<int> (GL::ETopCamera, m_cameraArgs);
-}
-
-void OverlayDialog::slot_fpath()
-{
-	ui->filename->setText (QFileDialog::getOpenFileName (null, "Overlay image"));
-}
-
-void OverlayDialog::slot_help()
-{
-	showDocumentation (g_docs_overlays);
-}
-
-void OverlayDialog::slot_dimensionsChanged()
-{
-	bool enable = (ui->width->value() != 0) || (ui->height->value() != 0);
-	ui->buttonBox->button (QDialogButtonBox::Ok)->setEnabled (enable);
-}
-
-// =============================================================================
-// =============================================================================
-LDrawPathDialog::LDrawPathDialog (const bool validDefault, QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f),
-	m_validDefault (validDefault)
-{
-	ui = new Ui_LDPathUI;
-	ui->setupUi (this);
-	ui->status->setText ("---");
-
-	if (validDefault)
-		ui->heading->hide();
-	else
-	{
-		cancelButton()->setText ("Exit");
-		cancelButton()->setIcon (getIcon ("exit"));
-	}
-
-	okButton()->setEnabled (false);
-
-	connect (ui->path, SIGNAL (textEdited (QString)), this, SLOT (slot_tryConfigure()));
-	connect (ui->searchButton, SIGNAL (clicked()), this, SLOT (slot_findPath()));
-	connect (ui->buttonBox, SIGNAL (rejected()), this, validDefault ? SLOT (reject()) : SLOT (slot_exit()));
-	connect (ui->buttonBox, SIGNAL (accepted()), this, SLOT (slot_accept()));
-
-	setPath (io_ldpath);
-
-	if (validDefault)
-		slot_tryConfigure();
-}
-
-// =============================================================================
-// =============================================================================
-LDrawPathDialog::~LDrawPathDialog()
-{
-	delete ui;
-}
-
-QPushButton* LDrawPathDialog::okButton()
-{
-	return ui->buttonBox->button (QDialogButtonBox::Ok);
-}
-
-QPushButton* LDrawPathDialog::cancelButton()
-{
-	return ui->buttonBox->button (QDialogButtonBox::Cancel);
-}
-
-void LDrawPathDialog::setPath (QString path)
-{
-	ui->path->setText (path);
-}
-
-QString LDrawPathDialog::filename() const
-{
-	return ui->path->text();
-}
-
-// =============================================================================
-// =============================================================================
-void LDrawPathDialog::slot_findPath()
-{
-	QString newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path");
-
-	if (newpath.length() > 0 && newpath != filename())
-	{
-		setPath (newpath);
-		slot_tryConfigure();
-	}
-}
-
-// =============================================================================
-// =============================================================================
-void LDrawPathDialog::slot_exit()
-{
-	exit (0);
-}
-
-// =============================================================================
-// =============================================================================
-void LDrawPathDialog::slot_tryConfigure()
-{
-	if (LDPaths::tryConfigure (filename()) == false)
-	{
-		ui->status->setText (format ("<span style=\"color:#700; \">%1</span>", LDPaths::getError()));
-		okButton()->setEnabled (false);
-		return;
-	}
-
-	ui->status->setText ("<span style=\"color: #270; \">OK!</span>");
-	okButton()->setEnabled (true);
-}
-
-// =============================================================================
-// =============================================================================
-void LDrawPathDialog::slot_accept()
-{
-	Config::save();
-	accept();
-}
-
-// =============================================================================
-// =============================================================================
-OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f)
-{
-	ui = new Ui_OpenProgressUI;
-	ui->setupUi (this);
-	ui->progressText->setText ("Parsing...");
-	setNumLines (0);
-	m_progress = 0;
-}
-
-// =============================================================================
-// =============================================================================
-OpenProgressDialog::~OpenProgressDialog()
-{
-	delete ui;
-}
-
-// =============================================================================
-// =============================================================================
-void OpenProgressDialog::setNumLines (int const& a)
-{
-	m_numLines = a;
-	ui->progressBar->setRange (0, numLines());
-	updateValues();
-}
-
-// =============================================================================
-// =============================================================================
-void OpenProgressDialog::updateValues()
-{
-	ui->progressText->setText (format ("Parsing... %1 / %2", progress(), numLines()));
-	ui->progressBar->setValue (progress());
-}
-
-// =============================================================================
-// =============================================================================
-void OpenProgressDialog::updateProgress (int progress)
-{
-	setProgress (progress);
-	updateValues();
-}
-
-// =============================================================================
-// =============================================================================
-ExtProgPathPrompt::ExtProgPathPrompt (QString progName, QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f),
-	ui (new Ui_ExtProgPath)
-{
-	ui->setupUi (this);
-	QString labelText = ui->m_label->text();
-	labelText.replace ("<PROGRAM>", progName);
-	ui->m_label->setText (labelText);
-	connect (ui->m_findPath, SIGNAL (clicked (bool)), this, SLOT (findPath()));
-}
-
-// =============================================================================
-// =============================================================================
-ExtProgPathPrompt::~ExtProgPathPrompt()
-{
-	delete ui;
-}
-
-// =============================================================================
-// =============================================================================
-void ExtProgPathPrompt::findPath()
-{
-	QString path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter);
-
-	if (!path.isEmpty())
-		ui->m_path->setText (path);
-}
-
-// =============================================================================
-// =============================================================================
-QString ExtProgPathPrompt::getPath() const
-{
-	return ui->m_path->text();
-}
-
-// =============================================================================
-// =============================================================================
-AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f)
-{
-	Ui::AboutUI ui;
-	ui.setupUi (this);
-	ui.versionInfo->setText (APPNAME " " + QString (fullVersionString()));
-
-	QPushButton* mailButton = new QPushButton;
-	mailButton->setText (tr ("Contact"));
-	mailButton->setIcon (getIcon ("mail"));
-	ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole);
-	connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail()));
-
-	setWindowTitle (format (tr ("About %1"), APPNAME));
-}
-
-// =============================================================================
-// =============================================================================
-void AboutDialog::slot_mail()
-{
-	QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <arezey@gmail.com>?subject=LDForge"));
-}
-
-// =============================================================================
-// =============================================================================
-void bombBox (const QString& message)
-{
-	QDialog dlg (g_win);
-	Ui_BombBox ui;
-
-	ui.setupUi (&dlg);
-	ui.m_text->setText (message);
-	ui.buttonBox->button (QDialogButtonBox::Close)->setText (QObject::tr ("Damn it"));
-	dlg.exec();
-}
--- a/src/Dialogs.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QDialog>
-#include "Main.h"
-#include "Types.h"
-
-class Ui_ExtProgPath;
-class QRadioButton;
-class QCheckBox;
-class QProgressBar;
-class QGroupBox;
-class QDialogButtonBox;
-class QDoubleSpinBox;
-class QPushButton;
-class QLineEdit;
-class QSpinBox;
-class RadioGroup;
-class QLabel;
-class QAbstractButton;
-class Ui_OverlayUI;
-class Ui_LDPathUI;
-class Ui_OpenProgressUI;
-
-class OverlayDialog : public QDialog
-{
-	Q_OBJECT
-
-	public:
-		explicit OverlayDialog (QWidget* parent = null, Qt::WindowFlags f = 0);
-		virtual ~OverlayDialog();
-
-		QString         fpath() const;
-		int         ofsx() const;
-		int         ofsy() const;
-		double      lwidth() const;
-		double      lheight() const;
-		int         camera() const;
-
-	private:
-		Ui_OverlayUI* ui;
-		QList<Pair<QRadioButton*, int>> m_cameraArgs;
-
-	private slots:
-		void slot_fpath();
-		void slot_help();
-		void slot_dimensionsChanged();
-		void fillDefaults (int newcam);
-};
-
-// =============================================================================
-class LDrawPathDialog : public QDialog
-{
-	Q_OBJECT
-
-	public:
-		explicit LDrawPathDialog (const bool validDefault, QWidget* parent = null, Qt::WindowFlags f = 0);
-		virtual ~LDrawPathDialog();
-		QString filename() const;
-		void setPath (QString path);
-
-	private:
-		Q_DISABLE_COPY (LDrawPathDialog)
-		const bool m_validDefault;
-		Ui_LDPathUI* ui;
-		QPushButton* okButton();
-		QPushButton* cancelButton();
-
-	private slots:
-		void slot_findPath();
-		void slot_tryConfigure();
-		void slot_exit();
-		void slot_accept();
-};
-
-// =============================================================================
-class OpenProgressDialog : public QDialog
-{
-	Q_OBJECT
-	PROPERTY (public,	int, progress,	setProgress,	STOCK_WRITE)
-	PROPERTY (public,	int, numLines,	setNumLines,	CUSTOM_WRITE)
-
-	public:
-		explicit OpenProgressDialog (QWidget* parent = null, Qt::WindowFlags f = 0);
-		virtual ~OpenProgressDialog();
-
-	public slots:
-		void updateProgress (int progress);
-
-	private:
-		Ui_OpenProgressUI* ui;
-
-		void updateValues();
-};
-
-// =============================================================================
-class ExtProgPathPrompt : public QDialog
-{
-	Q_OBJECT
-
-	public:
-		explicit ExtProgPathPrompt (QString progName, QWidget* parent = 0, Qt::WindowFlags f = 0);
-		virtual ~ExtProgPathPrompt();
-		QString getPath() const;
-
-	public slots:
-		void findPath();
-
-	private:
-		Ui_ExtProgPath* ui;
-};
-
-// =============================================================================
-class AboutDialog : public QDialog
-{
-	Q_OBJECT
-
-	public:
-		AboutDialog (QWidget* parent = null, Qt::WindowFlags f = 0);
-
-	private slots:
-		void slot_mail();
-};
-
-void bombBox (const QString& message);
--- a/src/Document.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1437 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QMessageBox>
-#include <QFileDialog>
-#include <QDir>
-#include <QApplication>
-#include "Main.h"
-#include "Configuration.h"
-#include "Document.h"
-#include "Misc.h"
-#include "MainWindow.h"
-#include "EditHistory.h"
-#include "Dialogs.h"
-#include "GLRenderer.h"
-#include "misc/InvokationDeferer.h"
-
-cfg (String, io_ldpath, "");
-cfg (List, io_recentfiles, {});
-extern_cfg (String, net_downloadpath);
-extern_cfg (Bool, gl_logostuds);
-
-static bool g_loadingMainFile = false;
-static const int g_maxRecentFiles = 10;
-static bool g_aborted = false;
-static LDDocumentPointer g_logoedStud = null;
-static LDDocumentPointer g_logoedStud2 = null;
-
-LDDocument* LDDocument::m_curdoc = null;
-
-const QStringList g_specialSubdirectories ({ "s", "48", "8" });
-
-// =============================================================================
-//
-namespace LDPaths
-{
-	static QString pathError;
-
-	struct
-	{
-		QString LDConfigPath;
-		QString partsPath, primsPath;
-	} pathInfo;
-
-	void initPaths()
-	{
-		if (!tryConfigure (io_ldpath))
-		{
-			LDrawPathDialog dlg (false);
-
-			if (!dlg.exec())
-				exit (0);
-
-			io_ldpath = dlg.filename();
-		}
-	}
-
-	bool tryConfigure (QString path)
-	{
-		QDir dir;
-
-		if (!dir.cd (path))
-		{
-			pathError = "Directory does not exist.";
-			return false;
-		}
-
-		QStringList mustHave = { "LDConfig.ldr", "parts", "p" };
-		QStringList contents = dir.entryList (mustHave);
-
-		if (contents.size() != mustHave.size())
-		{
-			pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/.";
-			return false;
-		}
-
-		pathInfo.partsPath = format ("%1" DIRSLASH "parts", path);
-		pathInfo.LDConfigPath = format ("%1" DIRSLASH "LDConfig.ldr", path);
-		pathInfo.primsPath = format ("%1" DIRSLASH "p", path);
-
-		return true;
-	}
-
-	// Accessors
-	QString getError()
-	{
-		return pathError;
-	}
-
-	QString ldconfig()
-	{
-		return pathInfo.LDConfigPath;
-	}
-
-	QString prims()
-	{
-		return pathInfo.primsPath;
-	}
-
-	QString parts()
-	{
-		return pathInfo.partsPath;
-	}
-}
-
-// =============================================================================
-//
-LDDocument::LDDocument() :
-	m_gldata (new LDGLData)
-{
-	setImplicit (true);
-	setSavePosition (-1);
-	setTabIndex (-1);
-	setHistory (new History);
-	history()->setDocument (this);
-}
-
-// =============================================================================
-//
-LDDocument::~LDDocument()
-{
-	// Remove this file from the list of files. This MUST be done FIRST, otherwise
-	// a ton of other functions will think this file is still valid when it is not!
-	g_loadedFiles.removeOne (this);
-
-	m_history->setIgnoring (true);
-
-	// Clear everything from the model
-	for (LDObject* obj : objects())
-		obj->destroy();
-
-	// Clear the cache as well
-	for (LDObject* obj : cache())
-		obj->destroy();
-
-	delete m_history;
-	delete m_gldata;
-
-	// If we just closed the current file, we need to set the current
-	// file as something else.
-	if (this == getCurrentDocument())
-	{
-		bool found = false;
-
-		// Try find an explicitly loaded file - if we can't find one,
-		// we need to create a new file to switch to.
-		for (LDDocument* file : g_loadedFiles)
-		{
-			if (!file->isImplicit())
-			{
-				LDDocument::setCurrent (file);
-				found = true;
-				break;
-			}
-		}
-
-		if (!found)
-			newFile();
-	}
-
-	if (this == g_logoedStud)
-		g_logoedStud = null;
-	elif (this == g_logoedStud2)
-		g_logoedStud2 = null;
-
-	g_win->updateDocumentList();
-	print ("Closed %1", name());
-}
-
-// =============================================================================
-//
-LDDocument* findDocument (QString name)
-{
-	for (LDDocument * file : g_loadedFiles)
-		if (!file->name().isEmpty() && file->name() == name)
-			return file;
-
-	return null;
-}
-
-// =============================================================================
-//
-QString dirname (QString path)
-{
-	long lastpos = path.lastIndexOf (DIRSLASH);
-
-	if (lastpos > 0)
-		return path.left (lastpos);
-
-#ifndef _WIN32
-	if (path[0] == DIRSLASH_CHAR)
-		return DIRSLASH;
-#endif // _WIN32
-
-	return "";
-}
-
-// =============================================================================
-//
-QString basename (QString path)
-{
-	long lastpos = path.lastIndexOf (DIRSLASH);
-
-	if (lastpos != -1)
-		return path.mid (lastpos + 1);
-
-	return path;
-}
-
-// =============================================================================
-//
-static QString findLDrawFilePath (QString relpath, bool subdirs)
-{
-	QString fullPath;
-
-	// LDraw models use Windows-style path separators. If we're not on Windows,
-	// replace the path separator now before opening any files. Qt expects
-	// forward-slashes as directory separators.
-#ifndef WIN32
-	relpath.replace ("\\", "/");
-#endif // WIN32
-
-	// Try find it relative to other currently open documents. We want a file
-	// in the immediate vicinity of a current model to override stock LDraw stuff.
-	QString reltop = basename (dirname (relpath));
-
-	for (LDDocument* doc : g_loadedFiles)
-	{
-		if (doc->fullPath().isEmpty())
-			continue;
-
-		QString partpath = format ("%1/%2", dirname (doc->fullPath()), relpath);
-		QFile f (partpath);
-
-		if (f.exists())
-		{
-			// ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48
-			QString proptop = basename (dirname (partpath));
-
-			bool bogus = false;
-
-			for (QString s : g_specialSubdirectories)
-			{
-				if ((proptop == s && reltop != s) || (reltop == s && proptop != s))
-				{
-					bogus = true;
-					break;
-				}
-			}
-
-			if (!bogus)
-				return partpath;
-		}
-	}
-
-	if (QFile::exists (relpath))
-		return relpath;
-
-	// Try with just the LDraw path first
-	fullPath = format ("%1" DIRSLASH "%2", io_ldpath, relpath);
-
-	if (QFile::exists (fullPath))
-		return fullPath;
-
-	if (subdirs)
-	{
-		// Look in sub-directories: parts and p. Also look in net_downloadpath, since that's
-		// where we download parts from the PT to.
-		for (const QString& topdir : QList<QString> ({ io_ldpath, net_downloadpath }))
-		{
-			for (const QString& subdir : QList<QString> ({ "parts", "p" }))
-			{
-				fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath);
-
-				if (QFile::exists (fullPath))
-					return fullPath;
-			}
-		}
-	}
-
-	// Did not find the file.
-	return "";
-}
-
-QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer)
-{
-	print ("Opening %1...\n", relpath);
-	QString path = findLDrawFilePath (relpath, subdirs);
-
-	if (pathpointer != null)
-		*pathpointer = path;
-
-	if (path.isEmpty())
-		return null;
-
-	QFile* fp = new QFile (path);
-
-	if (fp->open (QIODevice::ReadOnly))
-		return fp;
-
-	fp->deleteLater();
-	return null;
-}
-
-// =============================================================================
-//
-void LDFileLoader::start()
-{
-	setDone (false);
-	setProgress (0);
-	setAborted (false);
-
-	if (isOnForeground())
-	{
-		g_aborted = false;
-
-		// Show a progress dialog if we're loading the main Document.here so we can
-		// show progress updates and keep the WM posted that we're still here.
-		// Of course we cannot exec() the dialog because then the dialog would
-		// block.
-		dlg = new OpenProgressDialog (g_win);
-		dlg->setNumLines (lines().size());
-		dlg->setModal (true);
-		dlg->show();
-
-		// Connect the loader in so we can show updates
-		connect (this, SIGNAL (workDone()), dlg, SLOT (accept()));
-		connect (dlg, SIGNAL (rejected()), this, SLOT (abort()));
-	}
-	else
-		dlg = null;
-
-	// Begin working
-	work (0);
-}
-
-// =============================================================================
-//
-void LDFileLoader::work (int i)
-{
-	// User wishes to abort, so stop here now.
-	if (isAborted())
-	{
-		for (LDObject* obj : m_objects)
-			obj->destroy();
-
-		m_objects.clear();
-		setDone (true);
-		return;
-	}
-
-	// Parse up to 300 lines per iteration
-	int max = i + 300;
-
-	for (; i < max && i < (int) lines().size(); ++i)
-	{
-		QString line = lines()[i];
-
-		// Trim the trailing newline
-		QChar c;
-
-		while (line.endsWith ("\n") || line.endsWith ("\r"))
-			line.chop (1);
-
-		LDObject* obj = parseLine (line);
-
-		// Check for parse errors and warn about tthem
-		if (obj->type() == LDObject::EError)
-		{
-			print ("Couldn't parse line #%1: %2", progress() + 1, static_cast<LDError*> (obj)->reason());
-
-			if (warnings() != null)
-				(*warnings())++;
-		}
-
-		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) lines().size()) - 1)
-	{
-		emit workDone();
-		setDone (true);
-		return;
-	}
-
-	// Otherwise, continue, by recursing back.
-	if (!isDone())
-	{
-		// If we have a dialog to show progress output to, we cannot just call
-		// work() again immediately as the dialog needs some processor cycles as
-		// well. Thus, take a detour through the event loop by using the
-		// meta-object system.
-		//
-		// This terminates the loop here and control goes back to the function
-		// which called the file loader. It will keep processing the event loop
-		// until we're ready (see loadFileContents), thus the event loop will
-		// eventually catch the invokation we throw here and send us back. Though
-		// it's not technically recursion anymore, more like a for loop. :P
-		if (isOnForeground())
-			QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i));
-		else
-			work (i);
-	}
-}
-
-// =============================================================================
-//
-void LDFileLoader::abort()
-{
-	setAborted (true);
-
-	if (isOnForeground())
-		g_aborted = true;
-}
-
-// =============================================================================
-//
-LDObjectList loadFileContents (QFile* fp, int* numWarnings, bool* ok)
-{
-	QStringList lines;
-	LDObjectList objs;
-
-	if (numWarnings)
-		*numWarnings = 0;
-
-	// Read in the lines
-	while (fp->atEnd() == false)
-		lines << QString::fromUtf8 (fp->readLine());
-
-	LDFileLoader* loader = new LDFileLoader;
-	loader->setWarnings (numWarnings);
-	loader->setLines (lines);
-	loader->setOnForeground (g_loadingMainFile);
-	loader->start();
-
-	// After start() returns, if the loader isn't done yet, it's delaying
-	// its next iteration through the event loop. We need to catch this here
-	// by telling the event loop to tick, which will tick the file loader again.
-	// We keep doing this until the file loader is ready.
-	while (loader->isDone() == false)
-		qApp->processEvents();
-
-	// If we wanted the success value, supply that now
-	if (ok)
-		*ok = !loader->isAborted();
-
-	objs = loader->objects();
-	return objs;
-}
-
-// =============================================================================
-//
-LDDocument* openDocument (QString path, bool search)
-{
-	// Convert the file name to lowercase since some parts contain uppercase
-	// file names. I'll assume here that the library will always use lowercase
-	// file names for the actual parts..
-	QFile* fp;
-	QString fullpath;
-
-	if (search)
-		fp = openLDrawFile (path.toLower(), true, &fullpath);
-	else
-	{
-		fp = new QFile (path);
-		fullpath = path;
-
-		if (!fp->open (QIODevice::ReadOnly))
-		{
-			delete fp;
-			return null;
-		}
-	}
-
-	if (!fp)
-		return null;
-
-	LDDocument* load = new LDDocument;
-	load->setFullPath (fullpath);
-	load->setName (LDDocument::shortenName (load->fullPath()));
-	dprint ("name: %1 (%2)", load->name(), load->fullPath());
-	g_loadedFiles << load;
-
-	// Don't take the file loading as actual edits to the file
-	load->history()->setIgnoring (true);
-
-	int numWarnings;
-	bool ok;
-	LDObjectList objs = loadFileContents (fp, &numWarnings, &ok);
-	fp->close();
-	fp->deleteLater();
-
-	if (!ok)
-	{
-		g_loadedFiles.removeOne (load);
-		delete load;
-		return null;
-	}
-
-	load->addObjects (objs);
-
-	if (g_loadingMainFile)
-	{
-		LDDocument::setCurrent (load);
-		g_win->R()->setDocument (load);
-		print (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings);
-	}
-
-	load->history()->setIgnoring (false);
-	return load;
-}
-
-// =============================================================================
-//
-bool LDDocument::isSafeToClose()
-{
-	typedef QMessageBox msgbox;
-	setlocale (LC_ALL, "C");
-
-	// If we have unsaved changes, warn and give the option of saving.
-	if (hasUnsavedChanges())
-	{
-		QString message = format (tr ("There are unsaved changes to %1. Should it be saved?"),
-			(name().length() > 0) ? name() : tr ("<anonymous>"));
-
-		int button = msgbox::question (g_win, tr ("Unsaved Changes"), message,
-			(msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel);
-
-		switch (button)
-		{
-			case msgbox::Yes:
-			{
-				// If we don't have a file path yet, we have to ask the user for one.
-				if (name().length() == 0)
-				{
-					QString newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"),
-						getCurrentDocument()->name(), tr ("LDraw files (*.dat *.ldr)"));
-
-					if (newpath.length() == 0)
-						return false;
-
-					setName (newpath);
-				}
-
-				if (!save())
-				{
-					message = format (tr ("Failed to save %1 (%2)\nDo you still want to close?"),
-						name(), strerror (errno));
-
-					if (msgbox::critical (g_win, tr ("Save Failure"), message,
-						(msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No)
-					{
-						return false;
-					}
-				}
-			} break;
-
-			case msgbox::Cancel:
-				return false;
-
-			default:
-				break;
-		}
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-void closeAll()
-{
-	// Remove all loaded files and the objects they contain
-	QList<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()->setDocument (f);
-	g_win->doFullRefresh();
-	g_win->updateTitle();
-	g_win->updateActions();
-}
-
-// =============================================================================
-//
-void addRecentFile (QString path)
-{
-	auto& rfiles = io_recentfiles;
-	int idx = rfiles.indexOf (path);
-
-	// If this file already is in the list, pop it out.
-	if (idx != -1)
-	{
-		if (rfiles.size() == 1)
-			return; // only recent file - abort and do nothing
-
-		// Pop it out.
-		rfiles.removeAt (idx);
-	}
-
-	// If there's too many recent files, drop one out.
-	while (rfiles.size() > (g_maxRecentFiles - 1))
-		rfiles.removeAt (0);
-
-	// Add the file
-	rfiles << path;
-
-	Config::save();
-	g_win->updateRecentFilesMenu();
-}
-
-// =============================================================================
-// Open an LDraw file and set it as the main model
-// =============================================================================
-void openMainFile (QString path)
-{
-	g_loadingMainFile = true;
-
-	// If there's already a file with the same name, this file must replace it.
-	LDDocument* documentToReplace = null;
-	QString shortName = LDDocument::shortenName (path);
-
-	for (LDDocument* doc : g_loadedFiles)
-	{
-		if (doc->name() == shortName)
-		{
-			documentToReplace = doc;
-			break;
-		}
-	}
-
-	// We cannot open this file if the document this would replace is not
-	// safe to close.
-	if (documentToReplace != null && documentToReplace->isSafeToClose() == false)
-	{
-		g_loadingMainFile = false;
-		return;
-	}
-
-	LDDocument* file = openDocument (path, false);
-
-	if (!file)
-	{
-		// Loading failed, thus drop down to a new file since we
-		// closed everything prior.
-		newFile();
-
-		if (!g_aborted)
-		{
-			// Tell the user loading failed.
-			setlocale (LC_ALL, "C");
-			critical (format (QObject::tr ("Failed to open %1: %2"), path, strerror (errno)));
-		}
-
-		g_loadingMainFile = false;
-		return;
-	}
-
-	file->setImplicit (false);
-
-	// Replace references to the old file with the new file.
-	if (documentToReplace != null)
-	{
-		for (LDDocumentPointer* ptr : documentToReplace->references())
-		{	dprint ("ptr: %1 (%2)\n",
-				ptr, ptr->pointer() ? ptr->pointer()->name() : "<null>");
-
-			*ptr = file;
-		}
-
-		assert (documentToReplace->references().isEmpty());
-		delete documentToReplace;
-	}
-
-	// If we have an anonymous, unchanged file open as the only open file
-	// (aside of the one we just opened), close it now.
-	LDDocument::closeInitialFile();
-
-	// Rebuild the object tree view now.
-	LDDocument::setCurrent (file);
-	g_win->doFullRefresh();
-
-	// Add it to the recent files list.
-	addRecentFile (path);
-	g_loadingMainFile = false;
-}
-
-// =============================================================================
-//
-bool LDDocument::save (QString savepath)
-{
-	if (!savepath.length())
-		savepath = fullPath();
-
-	QFile f (savepath);
-
-	if (!f.open (QIODevice::WriteOnly))
-		return false;
-
-	// If the second object in the list holds the file name, update that now.
-	// Only do this if the file is explicitly open.
-	LDObject* nameObject = getObject (1);
-
-	if (!isImplicit() && nameObject != null && nameObject->type() == LDObject::EComment)
-	{
-		LDComment* nameComment = static_cast<LDComment*> (nameObject);
-
-		if (nameComment->text().left (6) == "Name: ")
-		{
-			QString newname = shortenName (savepath);
-			nameComment->setText (format ("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 : objects())
-		f.write ((obj->asText() + "\r\n").toUtf8());
-
-	// File is saved, now clean up.
-	f.close();
-
-	// We have successfully saved, update the save position now.
-	setSavePosition (history()->position());
-	setFullPath (savepath);
-	setName (shortenName (savepath));
-
-	g_win->updateDocumentListItem (this);
-	g_win->updateTitle();
-	return true;
-}
-
-// =============================================================================
-//
-class LDParseError : public std::exception
-{
-	PROPERTY (private, QString,	error,	setError,	STOCK_WRITE)
-	PROPERTY (private, QString,	line,	setLine,	STOCK_WRITE)
-
-	public:
-		LDParseError (QString line, QString a) :
-			m_error (a),
-			m_line (line) {}
-
-		const char* what() const throw()
-		{
-			return qPrintable (error());
-		}
-};
-
-// =============================================================================
-//
-void checkTokenCount (QString line, const QStringList& tokens, int num)
-{
-	if (tokens.size() != num)
-		throw LDParseError (line, format ("Bad amount of tokens, expected %1, got %2", num, tokens.size()));
-}
-
-// =============================================================================
-//
-void checkTokenNumbers (QString line, const QStringList& tokens, int min, int max)
-{
-	bool ok;
-
-	// Check scientific notation, e.g. 7.99361e-15
-	QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+");
-
-	for (int i = min; i <= max; ++i)
-	{
-		tokens[i].toDouble (&ok);
-
-		if (!ok && !scient.exactMatch (tokens[i]))
-			throw LDParseError (line, format ("Token #%1 was `%2`, expected a number (matched length: %3)", (i + 1), tokens[i], scient.matchedLength()));
-	}
-}
-
-// =============================================================================
-//
-static Vertex parseVertex (QStringList& s, const int n)
-{
-	Vertex v;
-
-	for_axes (ax)
-		v[ax] = s[n + ax].toDouble();
-
-	return v;
-}
-
-// =============================================================================
-// This is the LDraw code parser function. It takes in a string containing LDraw
-// code and returns the object parsed from it. parseLine never returns null,
-// the object will be LDError if it could not be parsed properly.
-// =============================================================================
-LDObject* parseLine (QString line)
-{
-	try
-	{
-		QStringList tokens = line.split (" ", QString::SkipEmptyParts);
-
-		if (tokens.size() <= 0)
-		{
-			// Line was empty, or only consisted of whitespace
-			return new LDEmpty;
-		}
-
-		if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false)
-			throw LDParseError (line, "Illogical line code");
-
-		int num = tokens[0][0].digitValue();
-
-		switch (num)
-		{
-			case 0:
-			{
-				// Comment
-				QString comm = line.mid (line.indexOf ("0") + 1).simplified();
-
-				// Handle BFC statements
-				if (tokens.size() > 2 && tokens[1] == "BFC")
-				{
-					for (int i = 0; i < LDBFC::NumStatements; ++i)
-						if (comm == format ("BFC %1", LDBFC::k_statementStrings [i]))
-							return new LDBFC ( (LDBFC::Statement) i);
-
-					// MLCAD is notorious for stuffing these statements in parts it
-					// creates. The above block only handles valid statements, so we
-					// need to handle MLCAD-style invertnext, clip and noclip separately.
-					struct
-					{
-						QString			a;
-						LDBFC::Statement	b;
-					} BFCData[] =
-					{
-						{ "INVERTNEXT", LDBFC::InvertNext },
-						{ "NOCLIP", LDBFC::NoClip },
-						{ "CLIP", LDBFC::Clip }
-					};
-
-					for (const auto& i : BFCData)
-						if (comm == "BFC CERTIFY " + i.a)
-							return new LDBFC (i.b);
-				}
-
-				if (tokens.size() > 2 && tokens[1] == "!LDFORGE")
-				{
-					// Handle LDForge-specific types, they're embedded into comments too
-					if (tokens[2] == "VERTEX")
-					{
-						// Vertex (0 !LDFORGE VERTEX)
-						checkTokenCount (line, tokens, 7);
-						checkTokenNumbers (line, tokens, 3, 6);
-
-						LDVertex* obj = new LDVertex;
-						obj->setColor (tokens[3].toLong());
-
-						for_axes (ax)
-							obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6
-
-						return obj;
-					} elif (tokens[2] == "OVERLAY")
-					{
-						checkTokenCount (line, tokens, 9);;
-						checkTokenNumbers (line, tokens, 5, 8);
-
-						LDOverlay* obj = new LDOverlay;
-						obj->setFileName (tokens[3]);
-						obj->setCamera (tokens[4].toLong());
-						obj->setX (tokens[5].toLong());
-						obj->setY (tokens[6].toLong());
-						obj->setWidth (tokens[7].toLong());
-						obj->setHeight (tokens[8].toLong());
-						return obj;
-					}
-				}
-
-				// Just a regular comment:
-				LDComment* obj = new LDComment;
-				obj->setText (comm);
-				return obj;
-			}
-
-			case 1:
-			{
-				// Subfile
-				checkTokenCount (line, tokens, 15);
-				checkTokenNumbers (line, tokens, 1, 13);
-
-				// Try open the file. Disable g_loadingMainFile temporarily since we're
-				// not loading the main file now, but the subfile in question.
-				bool tmp = g_loadingMainFile;
-				g_loadingMainFile = false;
-				LDDocument* load = getDocument (tokens[14]);
-				g_loadingMainFile = tmp;
-
-				// If we cannot open the file, mark it an error. Note we cannot use LDParseError
-				// here because the error object needs the document reference.
-				if (!load)
-				{
-					LDError* obj = new LDError (line, format ("Could not open %1", tokens[14]));
-					obj->setFileReferenced (tokens[14]);
-					return obj;
-				}
-
-				LDSubfile* obj = new LDSubfile;
-				obj->setColor (tokens[1].toLong());
-				obj->setPosition (parseVertex (tokens, 2));  // 2 - 4
-
-				Matrix transform;
-
-				for (int i = 0; i < 9; ++i)
-					transform[i] = tokens[i + 5].toDouble(); // 5 - 13
-
-				obj->setTransform (transform);
-				obj->setFileInfo (load);
-				return obj;
-			}
-
-			case 2:
-			{
-				checkTokenCount (line, tokens, 8);
-				checkTokenNumbers (line, tokens, 1, 7);
-
-				// Line
-				LDLine* obj = new LDLine;
-				obj->setColor (tokens[1].toLong());
-
-				for (int i = 0; i < 2; ++i)
-					obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 7
-
-				return obj;
-			}
-
-			case 3:
-			{
-				checkTokenCount (line, tokens, 11);
-				checkTokenNumbers (line, tokens, 1, 10);
-
-				// Triangle
-				LDTriangle* obj = new LDTriangle;
-				obj->setColor (tokens[1].toLong());
-
-				for (int i = 0; i < 3; ++i)
-					obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 10
-
-				return obj;
-			}
-
-			case 4:
-			case 5:
-			{
-				checkTokenCount (line, tokens, 14);
-				checkTokenNumbers (line, tokens, 1, 13);
-
-				// Quadrilateral / Conditional line
-				LDObject* obj;
-
-				if (num == 4)
-					obj = new LDQuad;
-				else
-					obj = new LDCondLine;
-
-				obj->setColor (tokens[1].toLong());
-
-				for (int i = 0; i < 4; ++i)
-					obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 13
-
-				return obj;
-			}
-
-			default: // Strange line we couldn't parse
-				throw LDError (line, "Unknown line code number");
-		}
-	}
-	catch (LDParseError& e)
-	{
-		return new LDError (e.line(), e.error());
-	}
-}
-
-// =============================================================================
-//
-LDDocument* getDocument (QString filename)
-{
-	// Try find the file in the list of loaded files
-	LDDocument* doc = findDocument (filename);
-
-	// If it's not loaded, try open it
-	if (!doc)
-		doc = openDocument (filename, true);
-
-	return doc;
-}
-
-// =============================================================================
-//
-void reloadAllSubfiles()
-{
-	if (!getCurrentDocument())
-		return;
-
-	g_loadedFiles.clear();
-	g_loadedFiles << getCurrentDocument();
-
-	// Go through all objects in the current file and reload the subfiles
-	for (LDObject* obj : getCurrentDocument()->objects())
-	{
-		if (obj->type() == LDObject::ESubfile)
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDDocument* fileInfo = getDocument (ref->fileInfo()->name());
-
-			if (fileInfo)
-				ref->setFileInfo (fileInfo);
-			else
-				ref->replace (new LDError (ref->asText(), format ("Could not open %1", ref->fileInfo()->name())));
-		}
-
-		// Reparse gibberish files. It could be that they are invalid because
-		// of loading errors. Circumstances may be different now.
-		if (obj->type() == LDObject::EError)
-			obj->replace (parseLine (static_cast<LDError*> (obj)->contents()));
-	}
-}
-
-// =============================================================================
-//
-int LDDocument::addObject (LDObject* obj)
-{
-	history()->add (new AddHistory (objects().size(), obj));
-	m_objects << obj;
-
-	if (obj->type() == LDObject::EVertex)
-		m_vertices << obj;
-
-#ifdef DEBUG
-	if (!isImplicit())
-		dprint ("Added object #%1 (%2)\n", obj->id(), obj->typeName());
-#endif
-
-	obj->setDocument (this);
-	return getObjectCount() - 1;
-}
-
-// =============================================================================
-//
-void LDDocument::addObjects (const LDObjectList objs)
-{
-	for (LDObject* obj : objs)
-		if (obj)
-			addObject (obj);
-}
-
-// =============================================================================
-//
-void LDDocument::insertObj (int pos, LDObject* obj)
-{
-	history()->add (new AddHistory (pos, obj));
-	m_objects.insert (pos, obj);
-	obj->setDocument (this);
-
-#ifdef DEBUG
-	if (!isImplicit())
-		dprint ("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos);
-#endif
-}
-
-// =============================================================================
-//
-void LDDocument::forgetObject (LDObject* obj)
-{
-	int idx = obj->lineNumber();
-	obj->unselect();
-	assert (m_objects[idx] == obj);
-
-	if (!history()->isIgnoring())
-		history()->add (new DelHistory (idx, obj));
-
-	m_objects.removeAt (idx);
-	obj->setDocument (null);
-}
-
-// =============================================================================
-//
-bool safeToCloseAll()
-{
-	for (LDDocument* f : g_loadedFiles)
-		if (!f->isSafeToClose())
-			return false;
-
-	return true;
-}
-
-// =============================================================================
-//
-void LDDocument::setObject (int idx, LDObject* obj)
-{
-	assert (idx >= 0 && idx < m_objects.size());
-
-	// Mark this change to history
-	if (!m_history->isIgnoring())
-	{
-		QString oldcode = getObject (idx)->asText();
-		QString newcode = obj->asText();
-		*m_history << new EditHistory (idx, oldcode, newcode);
-	}
-
-	m_objects[idx]->unselect();
-	m_objects[idx]->setDocument (null);
-	obj->setDocument (this);
-	m_objects[idx] = obj;
-}
-
-// =============================================================================
-//
-// Close all documents we don't need anymore
-//
-void LDDocument::closeUnused()
-{
-	for (LDDocument* file : g_loadedFiles)
-		if (file->isImplicit() && file->references().isEmpty())
-			delete file;
-}
-
-// =============================================================================
-//
-LDObject* LDDocument::getObject (int pos) const
-{
-	if (m_objects.size() <= pos)
-		return null;
-
-	return m_objects[pos];
-}
-
-// =============================================================================
-//
-int LDDocument::getObjectCount() const
-{
-	return objects().size();
-}
-
-// =============================================================================
-//
-bool LDDocument::hasUnsavedChanges() const
-{
-	return !isImplicit() && history()->position() != savePosition();
-}
-
-// =============================================================================
-//
-QString LDDocument::getDisplayName()
-{
-	if (!name().isEmpty())
-		return name();
-
-	if (!defaultName().isEmpty())
-		return "[" + defaultName() + "]";
-
-	return tr ("<anonymous>");
-}
-
-// =============================================================================
-//
-LDObjectList LDDocument::inlineContents (LDSubfile::InlineFlags flags)
-{
-	// Possibly substitute with logoed studs:
-	// stud.dat -> stud-logo.dat
-	// stud2.dat -> stud-logo2.dat
-	if (gl_logostuds && (flags & LDSubfile::RendererInline))
-	{
-		// Ensure logoed studs are loaded first
-		loadLogoedStuds();
-
-		if (name() == "stud.dat" && g_logoedStud)
-			return g_logoedStud->inlineContents (flags);
-		elif (name() == "stud2.dat" && g_logoedStud2)
-			return g_logoedStud2->inlineContents (flags);
-	}
-
-	LDObjectList objs, objcache;
-
-	bool deep = flags & LDSubfile::DeepInline,
-		 doCache = flags & LDSubfile::CacheInline;
-
-	if (m_needsCache)
-	{
-		m_cache.clear();
-		doCache = true;
-	}
-
-	// If we have this cached, just create a copy of that
-	if (deep && cache().isEmpty() == false)
-	{
-		for (LDObject* obj : cache())
-			objs << obj->createCopy();
-	}
-	else
-	{
-		if (!deep)
-			doCache = false;
-
-		for (LDObject* obj : objects())
-		{
-			// 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->type() == LDObject::ESubfile)
-			{
-				LDSubfile* ref = static_cast<LDSubfile*> (obj);
-
-				// We only want to cache immediate subfiles, so shed the caching
-				// flag when recursing deeper in hierarchy.
-				LDObjectList otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline));
-
-				for (LDObject* otherobj : otherobjs)
-				{
-					// Cache this object, if desired
-					if (doCache)
-						objcache << otherobj->createCopy();
-
-					objs << otherobj;
-				}
-			}
-			else
-			{
-				if (doCache)
-					objcache << obj->createCopy();
-
-				objs << obj->createCopy();
-			}
-		}
-
-		if (doCache)
-			setCache (objcache);
-	}
-
-	return objs;
-}
-
-// =============================================================================
-//
-LDDocument* LDDocument::current()
-{
-	return m_curdoc;
-}
-
-// =============================================================================
-// Sets the given file as the current one on display. At some point in time this
-// was an operation completely unheard of. ;)
-//
-// TODO: f can be temporarily null. This probably should not be the case.
-// =============================================================================
-void LDDocument::setCurrent (LDDocument* f)
-{
-	// Implicit files were loaded for caching purposes and must never be set
-	// current.
-	if (f && f->isImplicit())
-		return;
-
-	m_curdoc = f;
-
-	if (g_win && f)
-	{
-		// A ton of stuff needs to be updated
-		g_win->updateDocumentListItem (f);
-		g_win->buildObjList();
-		g_win->updateTitle();
-		g_win->R()->setDocument (f);
-		g_win->R()->repaint();
-		print ("Changed file to %1", f->getDisplayName());
-	}
-}
-
-// =============================================================================
-//
-int LDDocument::countExplicitFiles()
-{
-	int count = 0;
-
-	for (LDDocument* f : g_loadedFiles)
-		if (f->isImplicit() == false)
-			count++;
-
-	return count;
-}
-
-// =============================================================================
-// This little beauty closes the initial file that was open at first when opening
-// a new file over it.
-// =============================================================================
-void LDDocument::closeInitialFile()
-{
-	if (
-		countExplicitFiles() == 2 &&
-		g_loadedFiles[0]->name().isEmpty() &&
-		g_loadedFiles[1]->name().isEmpty() == false &&
-		!g_loadedFiles[0]->hasUnsavedChanges()
-	)
-		delete g_loadedFiles[0];
-}
-
-// =============================================================================
-//
-void loadLogoedStuds()
-{
-	if (g_logoedStud && g_logoedStud2)
-		return;
-
-	delete g_logoedStud;
-	delete g_logoedStud2;
-
-	g_logoedStud = openDocument ("stud-logo.dat", true);
-	g_logoedStud2 = openDocument ("stud2-logo.dat", true);
-
-	print (LDDocument::tr ("Logoed studs loaded.\n"));
-}
-
-// =============================================================================
-//
-void LDDocument::addToSelection (LDObject* obj) // [protected]
-{
-	if (obj->isSelected())
-		return;
-
-	assert (obj->document() == this);
-	m_sel << obj;
-	obj->setSelected (true);
-}
-
-// =============================================================================
-//
-void LDDocument::removeFromSelection (LDObject* obj) // [protected]
-{
-	if (!obj->isSelected())
-		return;
-
-	assert (obj->document() == this);
-	m_sel.removeOne (obj);
-	obj->setSelected (false);
-}
-
-// =============================================================================
-//
-void LDDocument::clearSelection()
-{
-	for (LDObject* obj : m_sel)
-		removeFromSelection (obj);
-
-	assert (m_sel.isEmpty());
-}
-
-// =============================================================================
-//
-const LDObjectList& LDDocument::getSelection() const
-{
-	return m_sel;
-}
-
-// =============================================================================
-//
-void LDDocument::swapObjects (LDObject* one, LDObject* other)
-{
-	int a = m_objects.indexOf (one);
-	int b = m_objects.indexOf (other);
-	assert (a != b && a != -1 && b != -1);
-	m_objects[b] = one;
-	m_objects[a] = other;
-	addToHistory (new SwapHistory (one->id(), other->id()));
-}
-
-// =============================================================================
-//
-QString LDDocument::shortenName (QString a) // [static]
-{
-	QString shortname = basename (a);
-	QString topdirname = basename (dirname (a));
-
-	if (g_specialSubdirectories.contains (topdirname))
-		shortname.prepend (topdirname + "\\");
-
-	return shortname;
-}
-
-// =============================================================================
-//
-void LDDocument::addReference (LDDocumentPointer* ptr)
-{
-	m_references << ptr;
-}
-
-// =============================================================================
-//
-void LDDocument::removeReference (LDDocumentPointer* ptr)
-{
-	m_references.removeOne (ptr);
-
-	if (references().isEmpty())
-		invokeLater (closeUnused);
-}
\ No newline at end of file
--- a/src/Document.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,240 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QObject>
-#include "Main.h"
-#include "LDObject.h"
-#include "EditHistory.h"
-
-class History;
-class OpenProgressDialog;
-class LDDocumentPointer;
-struct LDGLData;
-
-namespace LDPaths
-{
-	void initPaths();
-	bool tryConfigure (QString path);
-
-	QString ldconfig();
-	QString prims();
-	QString parts();
-	QString getError();
-}
-
-// =============================================================================
-//
-// This class stores a document 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
-{
-	public:
-		using ReferenceList = QList<LDDocumentPointer*>;
-
-		Q_OBJECT
-		PROPERTY (public,	QString,		name,			setName,			STOCK_WRITE)
-		PROPERTY (private,	LDObjectList,	objects, 		setObjects,			STOCK_WRITE)
-		PROPERTY (private,	LDObjectList,	cache, 			setCache,			STOCK_WRITE)
-		PROPERTY (private,	History*,		history,		setHistory,			STOCK_WRITE)
-		PROPERTY (private,	LDObjectList,	vertices,		setVertices,		STOCK_WRITE)
-		PROPERTY (private,	ReferenceList,	references,		setReferences,		STOCK_WRITE)
-		PROPERTY (public,	QString,		fullPath,		setFullPath,		STOCK_WRITE)
-		PROPERTY (public,	QString,		defaultName,	setDefaultName,		STOCK_WRITE)
-		PROPERTY (public,	bool,			isImplicit,		setImplicit,		STOCK_WRITE)
-		PROPERTY (public,	long,			savePosition,	setSavePosition,	STOCK_WRITE)
-		PROPERTY (public,	int,			tabIndex,		setTabIndex,		STOCK_WRITE)
-
-	public:
-		LDDocument();
-		~LDDocument();
-
-		int addObject (LDObject* obj); // Adds an object to this file at the end of the file.
-		void addObjects (const LDObjectList objs);
-		void clearSelection();
-		void forgetObject (LDObject* obj); // Deletes the given object from the object chain.
-		QString getDisplayName();
-		const LDObjectList& getSelection() const;
-		bool hasUnsavedChanges() const; // Does this document have unsaved changes?
-		LDObjectList inlineContents (LDSubfile::InlineFlags flags);
-		void insertObj (int pos, LDObject* obj);
-		int getObjectCount() const;
-		LDObject* getObject (int pos) const;
-		bool save (QString path = ""); // Saves this file to disk.
-		void swapObjects (LDObject* one, LDObject* other);
-		bool isSafeToClose(); // Perform safety checks. Do this before closing any files!
-		void setObject (int idx, LDObject* obj);
-		void addReference (LDDocumentPointer* ptr);
-		void removeReference (LDDocumentPointer* ptr);
-
-		inline LDDocument& operator<< (LDObject* obj)
-		{
-			addObject (obj);
-			return *this;
-		}
-
-		inline void addHistoryStep()
-		{
-			history()->addStep();
-		}
-
-		inline void undo()
-		{
-			history()->undo();
-		}
-
-		inline void redo()
-		{
-			history()->redo();
-		}
-
-		inline void clearHistory()
-		{
-			history()->clear();
-		}
-
-		inline void addToHistory (AbstractHistoryEntry* entry)
-		{
-			*history() << entry;
-		}
-
-		static void closeUnused();
-		static LDDocument* current();
-		static void setCurrent (LDDocument* f);
-		static void closeInitialFile();
-		static int countExplicitFiles();
-
-		// Turns a full path into a relative path
-		static QString shortenName (QString a);
-
-	protected:
-		void addToSelection (LDObject* obj);
-		void removeFromSelection (LDObject* obj);
-
-		LDGLData* getGLData()
-		{
-			return m_gldata;
-		}
-
-		friend class LDObject;
-		friend class GLRenderer;
-
-	private:
-		LDObjectList			m_sel;
-		LDGLData*				m_gldata;
-
-		// If set to true, next inline of this document discards the cache and
-		// re-builds it.
-		bool					m_needsCache;
-
-		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 (QString path);
-
-// Finds an OpenFile by name or null if not open
-LDDocument* findDocument (QString name);
-
-// Opens the given file and parses the LDraw code within. Returns a pointer
-// to the opened file or null on error.
-LDDocument* openDocument (QString path, bool search);
-
-// Opens the given file and returns a pointer to it, potentially looking in /parts and /p
-QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer = null);
-
-// 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 (QString 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 (QString filename);
-
-// Re-caches all subfiles.
-void reloadAllSubfiles();
-
-// Is it safe to close all files?
-bool safeToCloseAll();
-
-LDObjectList loadFileContents (QFile* f, int* numWarnings, bool* ok = null);
-
-extern QList<LDDocument*> g_loadedFiles;
-
-inline const LDObjectList& selection()
-{
-	return getCurrentDocument()->getSelection();
-}
-
-void addRecentFile (QString path);
-void loadLogoedStuds();
-QString basename (QString path);
-QString dirname (QString path);
-
-extern QList<LDDocument*> g_loadedFiles; // Vector of all currently opened files.
-
-// =============================================================================
-//
-// LDFileLoader
-//
-// 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,	LDObjectList,	objects,		setObjects,			STOCK_WRITE)
-	PROPERTY (private,	bool,			isDone,			setDone,			STOCK_WRITE)
-	PROPERTY (private,	int,			progress,		setProgress,		STOCK_WRITE)
-	PROPERTY (private,	bool,			isAborted,		setAborted,			STOCK_WRITE)
-	PROPERTY (public,	QStringList,	lines,			setLines,			STOCK_WRITE)
-	PROPERTY (public,	int*,			warnings,		setWarnings,		STOCK_WRITE)
-	PROPERTY (public,	bool,			isOnForeground,	setOnForeground,	STOCK_WRITE)
-
-	public slots:
-		void start();
-		void abort();
-
-	private:
-		OpenProgressDialog* dlg;
-
-	private slots:
-		void work (int i);
-
-	signals:
-		void progressUpdate (int progress);
-		void workDone();
-};
--- a/src/Documentation.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDialog>
-#include <QTextEdit>
-#include <QDialogButtonBox>
-#include <QBoxLayout>
-#include "Main.h"
-#include "Types.h"
-
-// =============================================================================
-// =============================================================================
-class DocumentViewer : public QDialog
-{
-	public:
-		explicit DocumentViewer (QWidget* parent = null, Qt::WindowFlags f = 0) : QDialog (parent, f)
-		{
-			te_text = new QTextEdit (this);
-			te_text->setMinimumSize (QSize (400, 300));
-			te_text->setReadOnly (true);
-
-			QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Close);
-			QVBoxLayout* layout = new QVBoxLayout (this);
-			layout->addWidget (te_text);
-			layout->addWidget (bbx_buttons);
-
-			connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject()));
-		}
-
-		void setText (const char* text)
-		{
-			te_text->setText (text);
-		}
-
-	private:
-		QTextEdit* te_text;
-};
-
-const char* g_docs_overlays =
-	"<h1>Overlay images</h1><br />"
-	"<p>" APPNAME " supports drawing transparent images over the part model. This "
-	"can be used to have, for instance, a photo of the part overlaid on top of the "
-	"model and use it for drawing curves somewhat accurately.</p>"
-	"<p>For this purpose, a specific photo has to be taken of the part; it should "
-	"represent the part as true as possible to the actual camera used for editing. "
-	"The image should be taken from straight above the part, at as an orthogonal "
-	"angle as possible. It is recommended to take a lot of pictures this way and "
-	"select the best candidate.</p>"
-	"<p>The image should then be cropped with the knowledge of the image's LDU "
-	"dimensions in mind. The offset should then be identified in the image in pixels.</p>"
-	"<p>Finally, use the \"Set Overlay Image\" dialog and fill in the details. The "
-	"overlay image should then be ready for use.";
-
-// =============================================================================
-// =============================================================================
-void showDocumentation (const char* text)
-{
-	DocumentViewer dlg;
-	dlg.setText (text);
-	dlg.exec();
-}
--- a/src/Documentation.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-extern const char* g_docs_overlays;
-void showDocumentation (const char* text);
--- a/src/EditHistory.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,202 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "EditHistory.h"
-#include "LDObject.h"
-#include "Document.h"
-#include "Misc.h"
-#include "MainWindow.h"
-#include "GLRenderer.h"
-
-// =============================================================================
-//
-History::History() :
-	m_position (-1) {}
-
-// =============================================================================
-//
-void History::undo()
-{
-	if (m_changesets.isEmpty() || position() == -1)
-		return;
-
-	// Don't take the changes done here as actual edits to the document
-	setIgnoring (true);
-
-	const Changeset& set = getChangeset (position());
-
-	// Iterate the list in reverse and undo all actions
-	for (int i = set.size() - 1; i >= 0; --i)
-	{
-		AbstractHistoryEntry* change = set[i];
-		change->undo();
-	}
-
-	m_position--;
-	g_win->refresh();
-	g_win->updateActions();
-	dprint ("Position is now %1", position());
-	setIgnoring (false);
-}
-
-// =============================================================================
-//
-void History::redo()
-{
-	if (position() == m_changesets.size())
-		return;
-
-	setIgnoring (true);
-	const Changeset& set = getChangeset (position() + 1);
-
-	// Redo things - in the order as they were done in the first place
-	for (const AbstractHistoryEntry* change : set)
-		change->redo();
-
-	setPosition (position() + 1);
-	g_win->refresh();
-	g_win->updateActions();
-	dprint ("Position is now %1", position());
-	setIgnoring (false);
-}
-
-// =============================================================================
-//
-void History::clear()
-{
-	for (Changeset set : m_changesets)
-		for (AbstractHistoryEntry* change : set)
-			delete change;
-
-	m_changesets.clear();
-	dprint ("History: cleared");
-}
-
-// =============================================================================
-//
-void History::addStep()
-{
-	if (m_currentChangeset.isEmpty())
-		return;
-
-	while (position() < getSize() - 1)
-	{
-		Changeset last = m_changesets.last();
-
-		for (AbstractHistoryEntry* entry : last)
-			delete entry;
-
-		m_changesets.removeLast();
-	}
-
-	dprint ("History: step added (%1 changes)", m_currentChangeset.size());
-	m_changesets << m_currentChangeset;
-	m_currentChangeset.clear();
-	setPosition (position() + 1);
-	g_win->updateActions();
-}
-
-// =============================================================================
-//
-void History::add (AbstractHistoryEntry* entry)
-{
-	if (isIgnoring())
-	{
-		delete entry;
-		return;
-	}
-
-	entry->setParent (this);
-	m_currentChangeset << entry;
-	dprint ("History: added entry of type %1", entry->getTypeName());
-}
-
-// =============================================================================
-//
-void AddHistory::undo() const
-{
-	LDObject* obj = parent()->document()->getObject (index());
-	obj->destroy();
-}
-
-// =============================================================================
-//
-void AddHistory::redo() const
-{
-	LDObject* obj = parseLine (code());
-	parent()->document()->insertObj (index(), obj);
-	g_win->R()->compileObject (obj);
-}
-
-// =============================================================================
-//
-DelHistory::DelHistory (int idx, LDObject* obj) :
-	m_index (idx),
-	m_code (obj->asText()) {}
-
-// =============================================================================
-// heh
-//
-void DelHistory::undo() const
-{
-	LDObject* obj = parseLine (code());
-	parent()->document()->insertObj (index(), obj);
-	g_win->R()->compileObject (obj);
-}
-
-// =============================================================================
-//
-void DelHistory::redo() const
-{
-	LDObject* obj = parent()->document()->getObject (index());
-	obj->destroy();
-}
-
-// =============================================================================
-//
-void EditHistory::undo() const
-{
-	LDObject* obj = getCurrentDocument()->getObject (index());
-	LDObject* newobj = parseLine (oldCode());
-	obj->replace (newobj);
-	g_win->R()->compileObject (newobj);
-}
-
-// =============================================================================
-//
-void EditHistory::redo() const
-{
-	LDObject* obj = getCurrentDocument()->getObject (index());
-	LDObject* newobj = parseLine (newCode());
-	obj->replace (newobj);
-	g_win->R()->compileObject (newobj);
-}
-
-// =============================================================================
-//
-void SwapHistory::undo() const
-{
-	LDObject::fromID (a)->swap (LDObject::fromID (b));
-}
-
-// =============================================================================
-//
-void SwapHistory::redo() const
-{
-	undo();
-}
\ No newline at end of file
--- a/src/EditHistory.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,175 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include "Main.h"
-#include "LDObject.h"
-
-#define IMPLEMENT_HISTORY_TYPE(N)							\
-	virtual ~N##History() {}								\
-	virtual void undo() const override;						\
-	virtual void redo() const override;						\
-															\
-	virtual History::EHistoryType getType() const override	\
-	{														\
-		return History::E##N##History;						\
-	}														\
-															\
-	virtual QString getTypeName() const						\
-	{														\
-		return #N;											\
-	}
-
-class AbstractHistoryEntry;
-
-// =============================================================================
-class History
-{
-	PROPERTY (private,	int,			position,	setPosition,	STOCK_WRITE)
-	PROPERTY (public,	LDDocument*,	document,	setDocument,	STOCK_WRITE)
-	PROPERTY (public,	bool,			isIgnoring,	setIgnoring,	STOCK_WRITE)
-
-	public:
-		typedef QList<AbstractHistoryEntry*> Changeset;
-
-		enum EHistoryType
-		{
-			EDelHistory,
-			EEditHistory,
-			EAddHistory,
-			EMoveHistory,
-			ESwapHistory,
-		};
-
-		History();
-		void undo();
-		void redo();
-		void clear();
-
-		void addStep();
-		void add (AbstractHistoryEntry* entry);
-
-		inline long getSize() const
-		{
-			return m_changesets.size();
-		}
-
-		inline History& operator<< (AbstractHistoryEntry* entry)
-		{
-			add (entry);
-			return *this;
-		}
-
-		inline const Changeset& getChangeset (long pos) const
-		{
-			return m_changesets[pos];
-		}
-
-	private:
-		Changeset m_currentChangeset;
-		QList<Changeset> m_changesets;
-};
-
-// =============================================================================
-//
-class AbstractHistoryEntry
-{
-	PROPERTY (public,	History*,	parent,	setParent,	STOCK_WRITE)
-
-	public:
-		virtual ~AbstractHistoryEntry() {}
-		virtual void undo() const = 0;
-		virtual void redo() const = 0;
-		virtual History::EHistoryType getType() const = 0;
-		virtual QString getTypeName() const = 0;
-};
-
-// =============================================================================
-//
-class DelHistory : public AbstractHistoryEntry
-{
-	PROPERTY (private,	int,		index,	setIndex,	STOCK_WRITE)
-	PROPERTY (private,	QString,	code,	setCode,	STOCK_WRITE)
-
-	public:
-		IMPLEMENT_HISTORY_TYPE (Del)
-		DelHistory (int idx, LDObject* obj);
-};
-
-// =============================================================================
-//
-class EditHistory : public AbstractHistoryEntry
-{
-	PROPERTY (private,	int, 		index,		setIndex,	STOCK_WRITE)
-	PROPERTY (private,	QString,	oldCode,	setOldCode,	STOCK_WRITE)
-	PROPERTY (private,	QString,	newCode,	setNewCode,	STOCK_WRITE)
-
-	public:
-		IMPLEMENT_HISTORY_TYPE (Edit)
-
-		EditHistory (int idx, QString oldCode, QString newCode) :
-			m_index (idx),
-			m_oldCode (oldCode),
-			m_newCode (newCode) {}
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class AddHistory : public AbstractHistoryEntry
-{
-	PROPERTY (private,	int,		index,	setIndex,	STOCK_WRITE)
-	PROPERTY (private,	QString,	code,	setCode,	STOCK_WRITE)
-
-	public:
-		IMPLEMENT_HISTORY_TYPE (Add)
-
-		AddHistory (int idx, LDObject* obj) :
-			m_index (idx),
-			m_code (obj->asText()) {}
-};
-
-// =============================================================================
-//
-class MoveHistory : public AbstractHistoryEntry
-{
-	public:
-		IMPLEMENT_HISTORY_TYPE (Move)
-
-		QList<int> indices;
-		Vertex dest;
-
-		MoveHistory (QList<int> indices, Vertex dest) :
-				indices (indices),
-				dest (dest) {}
-};
-
-// =============================================================================
-//
-class SwapHistory : public AbstractHistoryEntry
-{
-	public:
-		IMPLEMENT_HISTORY_TYPE (Swap)
-
-		SwapHistory (int a, int b) :
-			a (a),
-			b (b) {}
-
-	private:
-		int a, b;
-};
--- a/src/ExternalPrograms.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,696 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QProcess>
-#include <QTemporaryFile>
-#include <QDialog>
-#include <QDialogButtonBox>
-#include <QSpinBox>
-#include <QCheckBox>
-#include <QComboBox>
-#include <QGridLayout>
-#include "Main.h"
-#include "Configuration.h"
-#include "Misc.h"
-#include "MainWindow.h"
-#include "Document.h"
-#include "Widgets.h"
-#include "EditHistory.h"
-#include "ui_ytruder.h"
-#include "ui_intersector.h"
-#include "ui_rectifier.h"
-#include "ui_coverer.h"
-#include "ui_isecalc.h"
-#include "ui_edger2.h"
-#include "Dialogs.h"
-
-enum extprog
-{
-	Isecalc,
-	Intersector,
-	Coverer,
-	Ytruder,
-	Rectifier,
-	Edger2,
-};
-
-// =============================================================================
-//
-cfg (String, prog_isecalc, "");
-cfg (String, prog_intersector, "");
-cfg (String, prog_coverer, "");
-cfg (String, prog_ytruder, "");
-cfg (String, prog_rectifier, "");
-cfg (String, prog_edger2, "");
-
-QString* const g_extProgPaths[] =
-{
-	&prog_isecalc,
-	&prog_intersector,
-	&prog_coverer,
-	&prog_ytruder,
-	&prog_rectifier,
-	&prog_edger2,
-};
-
-#ifndef _WIN32
-cfg (Bool, prog_isecalc_wine, false);
-cfg (Bool, prog_intersector_wine, false);
-cfg (Bool, prog_coverer_wine, false);
-cfg (Bool, prog_ytruder_wine, false);
-cfg (Bool, prog_rectifier_wine, false);
-cfg (Bool, prog_edger2_wine, false);
-
-bool* const g_extProgWine[] =
-{
-	&prog_isecalc_wine,
-	&prog_intersector_wine,
-	&prog_coverer_wine,
-	&prog_ytruder_wine,
-	&prog_rectifier_wine,
-	&prog_edger2_wine,
-};
-#endif // _WIN32
-
-const char* g_extProgNames[] =
-{
-	"Isecalc",
-	"Intersector",
-	"Coverer",
-	"Ytruder",
-	"Rectifier",
-	"Edger2"
-};
-
-// =============================================================================
-//
-static bool mkTempFile (QTemporaryFile& tmp, QString& fname)
-{
-	if (!tmp.open())
-		return false;
-
-	fname = tmp.fileName();
-	tmp.close();
-	return true;
-}
-
-// =============================================================================
-//
-static bool checkProgPath (const extprog prog)
-{
-	QString& path = *g_extProgPaths[prog];
-
-	if (path.length() > 0)
-		return true;
-
-	ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]);
-
-	if (dlg->exec() && !dlg->getPath().isEmpty())
-	{
-		path = dlg->getPath();
-		return true;
-	}
-
-	return false;
-}
-
-// =============================================================================
-//
-static QString processErrorString (extprog prog, QProcess& proc)
-{
-	switch (proc.error())
-	{
-		case QProcess::FailedToStart:
-		{
-			QString wineblurb;
-
-#ifndef _WIN32
-			if (*g_extProgWine[prog])
-				wineblurb = "make sure Wine is installed and ";
-#endif
-
-			return format ("Program failed to start, %1check your permissions", wineblurb);
-		} break;
-
-		case QProcess::Crashed:
-			return "Crashed.";
-
-		case QProcess::WriteError:
-		case QProcess::ReadError:
-			return "I/O error.";
-
-		case QProcess::UnknownError:
-			return "Unknown error";
-
-		case QProcess::Timedout:
-			return format ("Timed out (30 seconds)");
-	}
-
-	return "";
-}
-
-// =============================================================================
-//
-static void writeObjects (const LDObjectList& objects, QFile& f)
-{
-	for (LDObject* obj : objects)
-	{
-		if (obj->type() == LDObject::ESubfile)
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDObjectList objs = ref->inlineContents (LDSubfile::DeepInline);
-
-			writeObjects (objs, f);
-
-			for (LDObject* obj : objs)
-				obj->destroy();
-		}
-		else
-			f.write ((obj->asText() + "\r\n").toUtf8());
-	}
-}
-
-// =============================================================================
-//
-static void writeObjects (const LDObjectList& objects, QString fname)
-{
-	// Write the input file
-	QFile f (fname);
-
-	if (!f.open (QIODevice::WriteOnly | QIODevice::Text))
-	{
-		critical (format ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString()));
-		return;
-	}
-
-	writeObjects (objects, f);
-	f.close();
-
-#ifdef DEBUG
-	QFile::copy (fname, "debug_lastInput");
-#endif
-}
-
-// =============================================================================
-//
-void writeSelection (QString fname)
-{
-	writeObjects (selection(), fname);
-}
-
-// =============================================================================
-//
-void writeColorGroup (const int colnum, QString fname)
-{
-	LDObjectList objects;
-
-	for (LDObject* obj : getCurrentDocument()->objects())
-	{
-		if (obj->isColored() == false || obj->color() != colnum)
-			continue;
-
-		objects << obj;
-	}
-
-	writeObjects (objects, fname);
-}
-
-// =============================================================================
-//
-bool runUtilityProcess (extprog prog, QString path, QString argvstr)
-{
-	QTemporaryFile input;
-	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
-
-#ifndef _WIN32
-	if (*g_extProgWine[prog])
-	{
-		argv.insert (0, path);
-		path = "wine";
-	}
-#endif // _WIN32
-
-	print ("Running command: %1 %2\n", path, argv.join (" "));
-
-	if (!input.open())
-		return false;
-
-	QProcess proc;
-
-	// Begin!
-	proc.setStandardInputFile (input.fileName());
-	proc.start (path, argv);
-
-	if (!proc.waitForStarted())
-	{
-		critical (format ("Couldn't start %1: %2\n", g_extProgNames[prog], processErrorString (prog, proc)));
-		return false;
-	}
-
-	// Write an enter, the utility tools all expect one
-	input.write ("\n");
-
-	// Wait while it runs
-	proc.waitForFinished();
-
-	QString err = "";
-
-	if (proc.exitStatus() != QProcess::NormalExit)
-		err = processErrorString (prog, proc);
-
-	// Check the return code
-	if (proc.exitCode() != 0)
-		err = format ("Program exited abnormally (return code %1).",  proc.exitCode());
-
-	if (!err.isEmpty())
-	{
-		critical (format ("%1 failed: %2\n", g_extProgNames[prog], err));
-		return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-static void insertOutput (QString fname, bool replace, QList<int> colorsToReplace)
-{
-#ifdef DEBUG
-	QFile::copy (fname, "./debug_lastOutput");
-#endif // RELEASE
-
-	// Read the output file
-	QFile f (fname);
-
-	if (!f.open (QIODevice::ReadOnly))
-	{
-		critical (format ("Couldn't open temporary file %1 for reading.\n", fname));
-		return;
-	}
-
-	LDObjectList objs = loadFileContents (&f, null);
-
-	// If we replace the objects, delete the selection now.
-	if (replace)
-		g_win->deleteSelection();
-
-	for (int colnum : colorsToReplace)
-		g_win->deleteByColor (colnum);
-
-	// Insert the new objects
-	getCurrentDocument()->clearSelection();
-
-	for (LDObject* obj : objs)
-	{
-		if (!obj->isScemantic())
-		{
-			obj->destroy();
-			continue;
-		}
-
-		getCurrentDocument()->addObject (obj);
-		obj->select();
-	}
-
-	g_win->doFullRefresh();
-}
-
-// =============================================================================
-// Interface for Ytruder
-// =============================================================================
-DEFINE_ACTION (Ytruder, 0)
-{
-	setlocale (LC_ALL, "C");
-
-	if (!checkProgPath (Ytruder))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::YtruderUI ui;
-	ui.setupUi (dlg);
-
-	if (!dlg->exec())
-		return;
-
-	// Read the user's choices
-	const enum { Distance, Symmetry, Projection, Radial } mode =
-		ui.mode_distance->isChecked()   ? Distance :
-		ui.mode_symmetry->isChecked()   ? Symmetry :
-		ui.mode_projection->isChecked() ? Projection : Radial;
-
-	const Axis axis =
-		ui.axis_x->isChecked() ? X :
-		ui.axis_y->isChecked() ? Y : Z;
-
-	const double depth = ui.planeDepth->value(),
-				 condAngle = ui.condAngle->value();
-
-	QTemporaryFile indat, outdat;
-	QString inDATName, outDATName;
-
-	// Make temp files for the input and output files
-	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
-		return;
-
-	// Compose the command-line arguments
-	QString argv = join (
-	{
-		(axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
-		(mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
-		depth,
-		"-a",
-		condAngle,
-		inDATName,
-		outDATName
-	});
-
-	writeSelection (inDATName);
-
-	if (!runUtilityProcess (Ytruder, prog_ytruder, argv))
-		return;
-
-	insertOutput (outDATName, false, {});
-}
-
-// =============================================================================
-// Rectifier interface
-// =============================================================================
-DEFINE_ACTION (Rectifier, 0)
-{
-	setlocale (LC_ALL, "C");
-
-	if (!checkProgPath (Rectifier))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::RectifierUI ui;
-	ui.setupUi (dlg);
-
-	if (!dlg->exec())
-		return;
-
-	QTemporaryFile indat, outdat;
-	QString inDATName, outDATName;
-
-	// Make temp files for the input and output files
-	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
-		return;
-
-	// Compose arguments
-	QString argv = join (
-	{
-		(!ui.cb_condense->isChecked()) ? "-q" : "",
-		(!ui.cb_subst->isChecked()) ? "-r" : "",
-		(ui.cb_condlineCheck->isChecked()) ? "-a" : "",
-		(ui.cb_colorize->isChecked()) ? "-c" : "",
-		"-t",
-		ui.dsb_coplthres->value(),
-		inDATName,
-		outDATName
-	});
-
-	writeSelection (inDATName);
-
-	if (!runUtilityProcess (Rectifier, prog_rectifier, argv))
-		return;
-
-	insertOutput (outDATName, true, {});
-}
-
-// =============================================================================
-// Intersector interface
-// =============================================================================
-DEFINE_ACTION (Intersector, 0)
-{
-	setlocale (LC_ALL, "C");
-
-	if (!checkProgPath (Intersector))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::IntersectorUI ui;
-	ui.setupUi (dlg);
-
-	makeColorComboBox (ui.cmb_incol);
-	makeColorComboBox (ui.cmb_cutcol);
-	ui.cb_repeat->setWhatsThis ("If this is set, " APPNAME " runs Intersector a second time with inverse files to cut the "
-								" cutter group with the input group. Both groups are cut by the intersection.");
-	ui.cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection.");
-
-	int inCol, cutCol;
-	const bool repeatInverse = ui.cb_repeat->isChecked();
-
-	forever
-	{
-		if (!dlg->exec())
-			return;
-
-		inCol = ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt();
-		cutCol =  ui.cmb_cutcol->itemData (ui.cmb_cutcol->currentIndex()).toInt();
-
-		if (inCol == cutCol)
-		{
-			critical ("Cannot use the same color group for both input and cutter!");
-			continue;
-		}
-
-		break;
-	}
-
-	// Five temporary files!
-	// indat = input group file
-	// cutdat = cutter group file
-	// outdat = primary output
-	// outdat2 = inverse output
-	// edgesdat = edges output (isecalc)
-	QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat;
-	QString inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
-
-	if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) ||
-			!mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) ||
-			!mkTempFile (edgesdat, edgesDATName))
-	{
-		return;
-	}
-
-	QString parms = join (
-	{
-		(ui.cb_colorize->isChecked()) ? "-c" : "",
-		(ui.cb_nocondense->isChecked()) ? "-t" : "",
-		"-s",
-		ui.dsb_prescale->value()
-	});
-
-	QString argv_normal = join (
-	{
-		parms,
-		inDATName,
-		cutDATName,
-		outDATName
-	});
-
-	QString argv_inverse = join (
-	{
-		parms,
-		cutDATName,
-		inDATName,
-		outDAT2Name
-	});
-
-	writeColorGroup (inCol, inDATName);
-	writeColorGroup (cutCol, cutDATName);
-
-	if (!runUtilityProcess (Intersector, prog_intersector, argv_normal))
-		return;
-
-	insertOutput (outDATName, false, {inCol});
-
-	if (repeatInverse && runUtilityProcess (Intersector, prog_intersector, argv_inverse))
-		insertOutput (outDAT2Name, false, {cutCol});
-
-	if (
-		ui.cb_edges->isChecked() &&
-		checkProgPath (Isecalc) &&
-		runUtilityProcess (Isecalc, prog_isecalc, join ( {inDATName, cutDATName, edgesDATName}))
-	)
-		insertOutput (edgesDATName, false, {});
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Coverer, 0)
-{
-	setlocale (LC_ALL, "C");
-
-	if (!checkProgPath (Coverer))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::CovererUI ui;
-	ui.setupUi (dlg);
-	makeColorComboBox (ui.cmb_col1);
-	makeColorComboBox (ui.cmb_col2);
-
-	int in1Col, in2Col;
-
-	forever
-	{
-		if (!dlg->exec())
-			return;
-
-		in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt();
-		in2Col = ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt();
-
-		if (in1Col == in2Col)
-		{
-			critical ("Cannot use the same color group for both input and cutter!");
-			continue;
-		}
-
-		break;
-	}
-
-	QTemporaryFile in1dat, in2dat, outdat;
-	QString in1DATName, in2DATName, outDATName;
-
-	if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
-		return;
-
-	QString argv = join (
-	{
-		(ui.cb_oldsweep->isChecked() ? "-s" : ""),
-		(ui.cb_reverse->isChecked() ? "-r" : ""),
-		(ui.dsb_segsplit->value() != 0 ? format ("-l %1", ui.dsb_segsplit->value()) : ""),
-		(ui.sb_bias->value() != 0 ? format ("-s %1", ui.sb_bias->value()) : ""),
-		in1DATName,
-		in2DATName,
-		outDATName
-	});
-
-	writeColorGroup (in1Col, in1DATName);
-	writeColorGroup (in2Col, in2DATName);
-
-	if (!runUtilityProcess (Coverer, prog_coverer, argv))
-		return;
-
-	insertOutput (outDATName, false, {});
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Isecalc, 0)
-{
-	setlocale (LC_ALL, "C");
-
-	if (!checkProgPath (Isecalc))
-		return;
-
-	Ui::IsecalcUI ui;
-	QDialog* dlg = new QDialog;
-	ui.setupUi (dlg);
-
-	makeColorComboBox (ui.cmb_col1);
-	makeColorComboBox (ui.cmb_col2);
-
-	int in1Col, in2Col;
-
-	// Run the dialog and validate input
-	forever
-	{
-		if (!dlg->exec())
-			return;
-
-		in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(),
-		in2Col = ui.cmb_col1->itemData (ui.cmb_col2->currentIndex()).toInt();
-
-		if (in1Col == in2Col)
-		{
-			critical ("Cannot use the same color group for both input and cutter!");
-			continue;
-		}
-
-		break;
-	}
-
-	QTemporaryFile in1dat, in2dat, outdat;
-	QString in1DATName, in2DATName, outDATName;
-
-	if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
-		return;
-
-	QString argv = join (
-	{
-		in1DATName,
-		in2DATName,
-		outDATName
-	});
-
-	writeColorGroup (in1Col, in1DATName);
-	writeColorGroup (in2Col, in2DATName);
-	runUtilityProcess (Isecalc, prog_isecalc, argv);
-	insertOutput (outDATName, false, {});
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Edger2, 0)
-{
-	setlocale (LC_ALL, "C");
-
-	if (!checkProgPath (Edger2))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::Edger2Dialog ui;
-	ui.setupUi (dlg);
-
-	if (!dlg->exec())
-		return;
-
-	QTemporaryFile in, out;
-	QString inName, outName;
-
-	if (!mkTempFile (in, inName) || !mkTempFile (out, outName))
-		return;
-
-	int unmatched = ui.unmatched->currentIndex();
-
-	QString argv = join (
-	{
-		format ("-p %1", ui.precision->value()),
-		format ("-af %1", ui.flatAngle->value()),
-		format ("-ac %1", ui.condAngle->value()),
-		format ("-ae %1", ui.edgeAngle->value()),
-		ui.delLines->isChecked()     ? "-de" : "",
-		ui.delCondLines->isChecked() ? "-dc" : "",
-		ui.colored->isChecked()      ? "-c" : "",
-		ui.bfc->isChecked()          ? "-b" : "",
-		ui.convex->isChecked()       ? "-cx" : "",
-		ui.concave->isChecked()      ? "-cv" : "",
-		unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""),
-		inName,
-		outName,
-	});
-
-	writeSelection (inName);
-
-	if (!runUtilityProcess (Edger2, prog_edger2, argv))
-		return;
-
-	insertOutput (outName, true, {});
-}
--- a/src/Format.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QString>
-#include "Types.h"
-
-//! \file Format.h
-//! Contains string formatting-related functions and classes.
-
-//!
-//! Converts a given value into a string that can be retrieved with text().
-//! Used as the argument type to the formatting functions, hence its name.
-//!
-class StringFormatArg
-{
-	public:
-		StringFormatArg (const QString& a) : m_text (a) {}
-		StringFormatArg (const char& a) : m_text (a) {}
-		StringFormatArg (const uchar& a) : m_text (a) {}
-		StringFormatArg (const QChar& a) : m_text (a) {}
-		StringFormatArg (int a) : m_text (QString::number (a)) {}
-		StringFormatArg (long a) : m_text (QString::number (a)) {}
-		StringFormatArg (const float& a) : m_text (QString::number (a)) {}
-		StringFormatArg (const double& a) : m_text (QString::number (a)) {}
-		StringFormatArg (const Vertex& a) : m_text (a.toString (false)) {}
-		StringFormatArg (const Matrix& a) : m_text (a.toString()) {}
-		StringFormatArg (const char* a) : m_text (a) {}
-
-		StringFormatArg (const void* a)
-		{
-			m_text.sprintf ("%p", a);
-		}
-
-		template<typename T>
-		StringFormatArg (const QList<T>& a)
-		{
-			m_text = "{";
-
-			for (const T& it : a)
-			{
-				if (&it != &a.first())
-					m_text += ", ";
-
-				StringFormatArg arg (it);
-				m_text += arg.text();
-			}
-
-			m_text += "}";
-		}
-
-		inline QString text() const
-		{
-			return m_text;
-		}
-
-	private:
-		QString m_text;
-};
-
-//!
-//! Helper function for \c format
-//!
-template<typename Arg1, typename... Rest>
-void formatHelper (QString& str, Arg1 arg1, Rest... rest)
-{
-	str = str.arg (StringFormatArg (arg1).text());
-	formatHelper (str, rest...);
-}
-
-//!
-//! Overload of \c formatHelper() with no template args
-//!
-static void formatHelper (QString& str) __attribute__ ((unused));
-static void formatHelper (QString& str)
-{
-	(void) str;
-}
-
-//!
-//! @brief Format the message with the given args.
-//!
-//! The formatting ultimately uses QString's arg() method to actually format
-//! the args so the format string should be prepared accordingly, with %1
-//! referring to the first arg, %2 to the second, etc.
-//!
-//! \param fmtstr The string to format
-//! \param args The args to format with
-//! \return The formatted string
-//!
-template<typename... Args>
-QString format (QString fmtstr, Args... args)
-{
-	formatHelper (fmtstr, args...);
-	return fmtstr;
-}
-
-//!
-//! From MessageLog.cc - declared here so that I don't need to include
-//! MessageLog.h here. Prints the given message to log.
-//!
-void printToLog (const QString& msg);
-
-//!
-//! Format and print the given args to the message log.
-//! \param fmtstr The string to format
-//! \param args The args to format with
-//!
-template<typename... Args>
-void print (QString fmtstr, Args... args)
-{
-	formatHelper (fmtstr, args...);
-	printToLog (fmtstr);
-}
-
-//!
-//! Format and print the given args to the given file descriptor
-//! \param fp The file descriptor to print to
-//! \param fmtstr The string to format
-//! \param args The args to format with
-//!
-template<typename... Args>
-void fprint (FILE* fp, QString fmtstr, Args... args)
-{
-	formatHelper (fmtstr, args...);
-	fprintf (fp, "%s", qPrintable (fmtstr));
-}
-
-//!
-//! Overload of \c fprint with a QIODevice
-//! \param dev The IO device to print to
-//! \param fmtstr The string to format
-//! \param args The args to format with
-//!
-template<typename... Args>
-void fprint (QIODevice& dev, QString fmtstr, Args... args)
-{
-	formatHelper (fmtstr, args...);
-	dev.write (fmtstr.toUtf8());
-}
-
-//!
-//! Exactly like print() except no-op in release builds.
-//! \param fmtstr The string to format
-//! \param args The args to format with
-//!
-template<typename... Args>
-void dprint (QString fmtstr, Args... args)
-{
-#ifndef RELEASE
-	formatHelper (fmtstr, args...);
-	printToLog (fmtstr);
-#else
-	(void) fmtstr;
-	(void) args;
-#endif
-}
--- a/src/GLRenderer.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2225 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QGLWidget>
-#include <QWheelEvent>
-#include <QMouseEvent>
-#include <QContextMenuEvent>
-#include <QInputDialog>
-#include <QToolTip>
-#include <QTimer>
-#include <GL/glu.h>
-
-#include "Main.h"
-#include "Configuration.h"
-#include "Document.h"
-#include "GLRenderer.h"
-#include "Colors.h"
-#include "MainWindow.h"
-#include "Misc.h"
-#include "EditHistory.h"
-#include "Dialogs.h"
-#include "AddObjectDialog.h"
-#include "MessageLog.h"
-#include "Primitives.h"
-#include "misc/RingFinder.h"
-
-static const LDFixedCameraInfo g_FixedCameras[6] =
-{
-	{{  1,  0, 0 }, X, Z, false, false },
-	{{  0,  0, 0 }, X, Y, false,  true },
-	{{  0,  1, 0 }, Z, Y,  true,  true },
-	{{ -1,  0, 0 }, X, Z, false,  true },
-	{{  0,  0, 0 }, X, Y,  true,  true },
-	{{  0, -1, 0 }, Z, Y, false,  true },
-};
-
-// Matrix templates for circle drawing. 2 is substituted with
-// the scale value, 1 is inverted to -1 if needed.
-static const Matrix g_circleDrawMatrixTemplates[3] =
-{
-	{ 2, 0, 0, 0, 1, 0, 0, 0, 2 },
-	{ 2, 0, 0, 0, 0, 2, 0, 1, 0 },
-	{ 0, 1, 0, 2, 0, 0, 0, 0, 2 },
-};
-
-cfg (String,	gl_bgcolor,				"#FFFFFF")
-cfg (String,	gl_maincolor,			"#A0A0A0")
-cfg (Float,		gl_maincolor_alpha,		1.0)
-cfg (String,	gl_selectcolor,			"#0080FF")
-cfg (Int,		gl_linethickness,		2)
-cfg (Bool,		gl_colorbfc,			false)
-cfg (Int,		gl_camera,				GLRenderer::EFreeCamera)
-cfg (Bool,		gl_blackedges,			false)
-cfg (Bool,		gl_axes,				false)
-cfg (Bool,		gl_wireframe,			false)
-cfg (Bool,		gl_logostuds,			false)
-cfg (Bool,		gl_aa,					true)
-cfg (Bool,		gl_linelengths,			true)
-cfg (Bool,		gl_drawangles,			false)
-
-// argh
-const char* g_CameraNames[7] =
-{
-	QT_TRANSLATE_NOOP ("GLRenderer",  "Top"),
-	QT_TRANSLATE_NOOP ("GLRenderer",  "Front"),
-	QT_TRANSLATE_NOOP ("GLRenderer",  "Left"),
-	QT_TRANSLATE_NOOP ("GLRenderer",  "Bottom"),
-	QT_TRANSLATE_NOOP ("GLRenderer",  "Back"),
-	QT_TRANSLATE_NOOP ("GLRenderer",  "Right"),
-	QT_TRANSLATE_NOOP ("GLRenderer",  "Free")
-};
-
-const GL::EFixedCamera g_Cameras[7] =
-{
-	GL::ETopCamera,
-	GL::EFrontCamera,
-	GL::ELeftCamera,
-	GL::EBottomCamera,
-	GL::EBackCamera,
-	GL::ERightCamera,
-	GL::EFreeCamera
-};
-
-// Definitions for visual axes, drawn on the screen
-const struct LDGLAxis
-{
-	const QColor col;
-	const Vertex vert;
-} g_GLAxes[3] =
-{
-	{ QColor (255,   0,   0), Vertex (10000, 0, 0) }, // X
-	{ QColor (80,  192,   0), Vertex (0, 10000, 0) }, // Y
-	{ QColor (0,   160, 192), Vertex (0, 0, 10000) }, // Z
-};
-
-static bool g_glInvert = false;
-static QList<int> g_warnedColors;
-
-// =============================================================================
-//
-GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent)
-{
-	m_isPicking = m_rangepick = false;
-	m_camera = (GL::EFixedCamera) gl_camera;
-	m_drawToolTip = false;
-	m_editMode = ESelectMode;
-	m_rectdraw = false;
-	m_panning = false;
-	setDocument (null);
-	setDrawOnly (false);
-	setMessageLog (null);
-	m_width = m_height = -1;
-	m_hoverpos = g_origin;
-
-	m_toolTipTimer = new QTimer (this);
-	m_toolTipTimer->setSingleShot (true);
-	connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer()));
-
-	m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
-	m_thinBorderPen = m_thickBorderPen;
-	m_thinBorderPen.setWidth (1);
-
-	// Init camera icons
-	for (const GL::EFixedCamera cam : g_Cameras)
-	{
-		QString iconname = format ("camera-%1", tr (g_CameraNames[cam]).toLower());
-
-		CameraIcon* info = &m_cameraIcons[cam];
-		info->img = new QPixmap (getIcon (iconname));
-		info->cam = cam;
-	}
-
-	calcCameraIcons();
-}
-
-// =============================================================================
-//
-GLRenderer::~GLRenderer()
-{
-	for (int i = 0; i < 6; ++i)
-		delete currentDocumentData().overlays[i].img;
-
-	for (CameraIcon& info : m_cameraIcons)
-		delete info.img;
-}
-
-// =============================================================================
-// Calculates the "hitboxes" of the camera icons so that we can tell when the
-// cursor is pointing at the camera icon.
-//
-void GLRenderer::calcCameraIcons()
-{
-	int i = 0;
-
-	for (CameraIcon& info : m_cameraIcons)
-	{
-		// MATH
-		const long x1 = (m_width - (info.cam != EFreeCamera ? 48 : 16)) + ((i % 3) * 16) - 1,
-			y1 = ((i / 3) * 16) + 1;
-
-		info.srcRect = QRect (0, 0, 16, 16);
-		info.destRect = QRect (x1, y1, 16, 16);
-		info.selRect = QRect (
-			info.destRect.x(),
-			info.destRect.y(),
-			info.destRect.width() + 1,
-			info.destRect.height() + 1
-		);
-
-		++i;
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::initGLData()
-{
-	glEnable (GL_BLEND);
-	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-	glEnable (GL_POLYGON_OFFSET_FILL);
-	glPolygonOffset (1.0f, 1.0f);
-
-	glEnable (GL_DEPTH_TEST);
-	glShadeModel (GL_SMOOTH);
-	glEnable (GL_MULTISAMPLE);
-
-	if (gl_aa)
-	{
-		glEnable (GL_LINE_SMOOTH);
-		glEnable (GL_POLYGON_SMOOTH);
-		glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
-		glHint (GL_POLYGON_SMOOTH_HINT, GL_NICEST);
-	} else
-	{
-		glDisable (GL_LINE_SMOOTH);
-		glDisable (GL_POLYGON_SMOOTH);
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::resetAngles()
-{
-	rot (X) = 30.0f;
-	rot (Y) = 325.f;
-	pan (X) = pan (Y) = rot (Z) = 0.0f;
-	zoomToFit();
-}
-
-// =============================================================================
-//
-void GLRenderer::resetAllAngles()
-{
-	EFixedCamera oldcam = camera();
-
-	for (int i = 0; i < 7; ++i)
-	{
-		setCamera ((EFixedCamera) i);
-		resetAngles();
-	}
-
-	setCamera (oldcam);
-}
-
-// =============================================================================
-//
-void GLRenderer::initializeGL()
-{
-	setBackground();
-
-	glLineWidth (gl_linethickness);
-
-	setAutoFillBackground (false);
-	setMouseTracking (true);
-	setFocusPolicy (Qt::WheelFocus);
-	compileAllObjects();
-}
-
-// =============================================================================
-//
-QColor GLRenderer::getMainColor()
-{
-	QColor col (gl_maincolor);
-
-	if (!col.isValid())
-		return QColor (0, 0, 0);
-
-	col.setAlpha (gl_maincolor_alpha * 255.f);
-	return col;
-}
-
-// =============================================================================
-//
-void GLRenderer::setBackground()
-{
-	QColor col (gl_bgcolor);
-
-	if (!col.isValid())
-		return;
-
-	col.setAlpha (255);
-
-	m_darkbg = luma (col) < 80;
-	m_bgcolor = col;
-	qglClearColor (col);
-}
-
-// =============================================================================
-//
-void GLRenderer::setObjectColor (LDObject* obj, const ListType list)
-{
-	QColor qcol;
-
-	if (!obj->isColored())
-		return;
-
-	if (list == GL::PickList)
-	{
-		// Make the color by the object's ID if we're picking, so we can make the
-		// ID again from the color we get from the picking results. Be sure to use
-		// the top level parent's index since we want a subfile's children point
-		// to the subfile itself.
-		long i = obj->topLevelParent()->id();
-
-		// Calculate a color based from this index. This method caters for
-		// 16777216 objects. I don't think that'll be exceeded anytime soon. :)
-		// ATM biggest is 53588.dat with 12600 lines.
-		double r = (i / 0x10000) % 0x100,
-			   g = (i / 0x100) % 0x100,
-			   b = i % 0x100;
-
-		qglColor (QColor (r, g, b));
-		return;
-	}
-
-	if ((list == BFCFrontList || list == BFCBackList) &&
-		obj->type() != LDObject::ELine &&
-		obj->type() != LDObject::ECondLine)
-	{
-		if (list == GL::BFCFrontList)
-			qcol = QColor (40, 192, 0);
-		else
-			qcol = QColor (224, 0, 0);
-	}
-	else
-	{
-		if (obj->color() == maincolor)
-			qcol = getMainColor();
-		else
-		{
-			LDColor* col = ::getColor (obj->color());
-
-			if (col)
-				qcol = col->faceColor;
-		}
-
-		if (obj->color() == edgecolor)
-		{
-			LDColor* col;
-
-			if (!gl_blackedges && obj->parent() && (col = ::getColor (obj->parent()->color())))
-				qcol = col->edgeColor;
-			else
-				qcol = (m_darkbg == false) ? Qt::black : Qt::white;
-		}
-
-		if (qcol.isValid() == false)
-		{
-			// The color was unknown. Use main color to make the object at least
-			// not appear pitch-black.
-			if (obj->color() != edgecolor)
-				qcol = getMainColor();
-
-			// Warn about the unknown colors, but only once.
-			for (int i : g_warnedColors)
-				if (obj->color() == i)
-					return;
-
-			print ("%1: Unknown color %2!\n", __func__, obj->color());
-			g_warnedColors << obj->color();
-			return;
-		}
-	}
-
-	int r = qcol.red(),
-		 g = qcol.green(),
-		 b = qcol.blue(),
-		 a = qcol.alpha();
-
-	if (obj->topLevelParent()->isSelected())
-	{
-		// Brighten it up for the select list.
-		QColor selcolor (gl_selectcolor);
-		r = (r + selcolor.red()) / 2;
-		g = (g + selcolor.green()) / 2;
-		b = (b + selcolor.blue()) / 2;
-	}
-
-	glColor4f (
-		((double) r) / 255.0f,
-		((double) g) / 255.0f,
-		((double) b) / 255.0f,
-		((double) a) / 255.0f);
-}
-
-// =============================================================================
-//
-void GLRenderer::refresh()
-{
-	update();
-	swapBuffers();
-}
-
-// =============================================================================
-//
-void GLRenderer::hardRefresh()
-{
-	compileAllObjects();
-	refresh();
-
-	glLineWidth (gl_linethickness);
-}
-
-// =============================================================================
-//
-void GLRenderer::resizeGL (int w, int h)
-{
-	m_width = w;
-	m_height = h;
-
-	calcCameraIcons();
-
-	glViewport (0, 0, w, h);
-	glMatrixMode (GL_PROJECTION);
-	glLoadIdentity();
-	gluPerspective (45.0f, (double) w / (double) h, 1.0f, 10000.0f);
-	glMatrixMode (GL_MODELVIEW);
-}
-
-// =============================================================================
-//
-void GLRenderer::drawGLScene()
-{
-	if (document() == null)
-		return;
-
-	if (gl_wireframe && !isPicking())
-		glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
-
-	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-	glEnable (GL_DEPTH_TEST);
-
-	if (m_camera != EFreeCamera)
-	{
-		glMatrixMode (GL_PROJECTION);
-		glPushMatrix();
-
-		glLoadIdentity();
-		glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f);
-		glTranslatef (pan (X), pan (Y), 0.0f);
-
-		if (m_camera != EFrontCamera && m_camera != EBackCamera)
-		{
-			glRotatef (90.0f, g_FixedCameras[camera()].glrotate[0],
-				g_FixedCameras[camera()].glrotate[1],
-				g_FixedCameras[camera()].glrotate[2]);
-		}
-
-		// Back camera needs to be handled differently
-		if (m_camera == GLRenderer::EBackCamera)
-		{
-			glRotatef (180.0f, 1.0f, 0.0f, 0.0f);
-			glRotatef (180.0f, 0.0f, 0.0f, 1.0f);
-		}
-	}
-	else
-	{
-		glMatrixMode (GL_MODELVIEW);
-		glPushMatrix();
-		glLoadIdentity();
-
-		glTranslatef (0.0f, 0.0f, -2.0f);
-		glTranslatef (pan (X), pan (Y), -zoom());
-		glRotatef (rot (X), 1.0f, 0.0f, 0.0f);
-		glRotatef (rot (Y), 0.0f, 1.0f, 0.0f);
-		glRotatef (rot (Z), 0.0f, 0.0f, 1.0f);
-	}
-
-	const GL::ListType list = (!isDrawOnly() && isPicking()) ? PickList : NormalList;
-
-	if (gl_colorbfc && !isPicking() && !isDrawOnly())
-	{
-		glEnable (GL_CULL_FACE);
-
-		for (LDObject* obj : document()->objects())
-		{
-			if (obj->isHidden())
-				continue;
-
-			glCullFace (GL_BACK);
-			glCallList (obj->glLists[BFCFrontList]);
-
-			glCullFace (GL_FRONT);
-			glCallList (obj->glLists[BFCBackList]);
-		}
-
-		glDisable (GL_CULL_FACE);
-	}
-	else
-	{
-		for (LDObject* obj : document()->objects())
-		{
-			if (obj->isHidden())
-				continue;
-
-			glCallList (obj->glLists[list]);
-		}
-	}
-
-	if (gl_axes && !isPicking() && !isDrawOnly())
-		glCallList (m_axeslist);
-
-	glPopMatrix();
-	glMatrixMode (GL_MODELVIEW);
-	glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
-}
-
-// =============================================================================
-//
-// This converts a 2D point on the screen to a 3D point in the model. If 'snap'
-// is true, the 3D point will snap to the current grid.
-//
-Vertex GLRenderer::coordconv2_3 (const QPoint& pos2d, bool snap) const
-{
-	assert (camera() != EFreeCamera);
-
-	Vertex pos3d;
-	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
-	const Axis axisX = cam->axisX;
-	const Axis axisY = cam->axisY;
-	const int negXFac = cam->negX ? -1 : 1,
-				negYFac = cam->negY ? -1 : 1;
-
-	// Calculate cx and cy - these are the LDraw unit coords the cursor is at.
-	double cx = (-m_virtWidth + ((2 * pos2d.x() * m_virtWidth) / m_width) - pan (X));
-	double cy = (m_virtHeight - ((2 * pos2d.y() * m_virtHeight) / m_height) - pan (Y));
-
-	if (snap)
-	{
-		cx = Grid::snap (cx, (Grid::Config) axisX);
-		cy = Grid::snap (cy, (Grid::Config) axisY);
-	}
-
-	cx *= negXFac;
-	cy *= negYFac;
-
-	roundToDecimals (cx, 4);
-	roundToDecimals (cy, 4);
-
-	// Create the vertex from the coordinates
-	pos3d[axisX] = cx;
-	pos3d[axisY] = cy;
-	pos3d[3 - axisX - axisY] = getDepthValue();
-	return pos3d;
-}
-
-// =============================================================================
-//
-// Inverse operation for the above - convert a 3D position to a 2D screen
-// position. Don't ask me how this code manages to work, I don't even know.
-//
-QPoint GLRenderer::coordconv3_2 (const Vertex& pos3d) const
-{
-	GLfloat m[16];
-	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
-	const Axis axisX = cam->axisX;
-	const Axis axisY = cam->axisY;
-	const int negXFac = cam->negX ? -1 : 1,
-				negYFac = cam->negY ? -1 : 1;
-
-	glGetFloatv (GL_MODELVIEW_MATRIX, m);
-
-	const double x = pos3d.x();
-	const double y = pos3d.y();
-	const double z = pos3d.z();
-
-	Vertex transformed;
-	transformed[X] = (m[0] * x) + (m[1] * y) + (m[2] * z) + m[3];
-	transformed[Y] = (m[4] * x) + (m[5] * y) + (m[6] * z) + m[7];
-	transformed[Z] = (m[8] * x) + (m[9] * y) + (m[10] * z) + m[11];
-
-	double rx = (((transformed[axisX] * negXFac) + m_virtWidth + pan (X)) * m_width) / (2 * m_virtWidth);
-	double ry = (((transformed[axisY] * negYFac) - m_virtHeight + pan (Y)) * m_height) / (2 * m_virtHeight);
-
-	return QPoint (rx, -ry);
-}
-
-// =============================================================================
-//
-void GLRenderer::paintEvent (QPaintEvent* ev)
-{
-	Q_UNUSED (ev)
-
-	makeCurrent();
-	m_virtWidth = zoom();
-	m_virtHeight = (m_height * m_virtWidth) / m_width;
-
-	initGLData();
-	drawGLScene();
-
-	const QPen textpen (m_darkbg ? Qt::white : Qt::black);
-	const QBrush polybrush (QColor (64, 192, 0, 128));
-	QPainter paint (this);
-	QFontMetrics metrics = QFontMetrics (QFont());
-	paint.setRenderHint (QPainter::HighQualityAntialiasing);
-
-	// If we wish to only draw the brick, stop here
-	if (isDrawOnly())
-		return;
-
-	if (m_camera != EFreeCamera && !isPicking())
-	{
-		// Paint the overlay image if we have one
-		const LDGLOverlay& overlay = currentDocumentData().overlays[m_camera];
-
-		if (overlay.img != null)
-		{
-			QPoint v0 = coordconv3_2 (currentDocumentData().overlays[m_camera].v0),
-					   v1 = coordconv3_2 (currentDocumentData().overlays[m_camera].v1);
-
-			QRect targRect (v0.x(), v0.y(), abs (v1.x() - v0.x()), abs (v1.y() - v0.y())),
-				  srcRect (0, 0, overlay.img->width(), overlay.img->height());
-			paint.drawImage (targRect, *overlay.img, srcRect);
-		}
-
-		// Paint the coordinates onto the screen.
-		QString text = format (tr ("X: %1, Y: %2, Z: %3"), m_hoverpos[X], m_hoverpos[Y], m_hoverpos[Z]);
-		QFontMetrics metrics = QFontMetrics (font());
-		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
-		paint.setPen (textpen);
-		paint.drawText (m_width - textSize.width(), m_height - 16, textSize.width(),
-			textSize.height(), Qt::AlignCenter, text);
-
-		QPen linepen = m_thinBorderPen;
-		linepen.setWidth (2);
-		linepen.setColor (luma (m_bgcolor) < 40 ? Qt::white : Qt::black);
-
-		// Mode-specific rendering
-		if (editMode() == EDrawMode)
-		{
-			QPoint poly[4];
-			Vertex poly3d[4];
-			int numverts = 4;
-
-			// Calculate polygon data
-			if (!m_rectdraw)
-			{
-				numverts = m_drawedVerts.size() + 1;
-				int i = 0;
-
-				for (Vertex& vert : m_drawedVerts)
-					poly3d[i++] = vert;
-
-				// Draw the cursor vertex as the last one in the list.
-				if (numverts <= 4)
-					poly3d[i] = m_hoverpos;
-				else
-					numverts = 4;
-			}
-			else
-			{
-				// Get vertex information from m_rectverts
-				if (m_drawedVerts.size() > 0)
-					for (int i = 0; i < numverts; ++i)
-						poly3d[i] = m_rectverts[i];
-				else
-					poly3d[0] = m_hoverpos;
-			}
-
-			// Convert to 2D
-			for (int i = 0; i < numverts; ++i)
-				poly[i] = coordconv3_2 (poly3d[i]);
-
-			if (numverts > 0)
-			{
-				// Draw the polygon-to-be
-				paint.setBrush (polybrush);
-				paint.drawPolygon (poly, numverts);
-
-				// Draw vertex blips
-				for (int i = 0; i < numverts; ++i)
-				{
-					QPoint& blip = poly[i];
-					paint.setPen (linepen);
-					drawBlip (paint, blip);
-
-					// Draw their coordinates
-					paint.setPen (textpen);
-					paint.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true));
-				}
-
-				// Draw line lenghts and angle info if appropriate
-				if (numverts >= 2)
-				{
-					int numlines = (m_drawedVerts.size() == 1) ? 1 : m_drawedVerts.size() + 1;
-					paint.setPen (textpen);
-
-					for (int i = 0; i < numlines; ++i)
-					{
-						const int j = (i + 1 < numverts) ? i + 1 : 0;
-						const int h = (i - 1 >= 0) ? i - 1 : numverts - 1;
-
-						if (gl_linelengths)
-						{
-							const QString label = QString::number (poly3d[i].distanceTo (poly3d[j]));
-							QPoint origin = QLineF (poly[i], poly[j]).pointAt (0.5).toPoint();
-							paint.drawText (origin, label);
-						}
-
-						if (gl_drawangles)
-						{
-							QLineF l0 (poly[h], poly[i]),
-								l1 (poly[i], poly[j]);
-
-							double angle = 180 - l0.angleTo (l1);
-
-							if (angle < 0)
-								angle = 180 - l1.angleTo (l0);
-
-							QString label = QString::number (angle) + QString::fromUtf8 (QByteArray ("\302\260"));
-							QPoint pos = poly[i];
-							pos.setY (pos.y() + metrics.height());
-
-							paint.drawText (pos, label);
-						}
-					}
-				}
-			}
-		}
-		elif (editMode() == ECircleMode)
-		{
-			// If we have not specified the center point of the circle yet, preview it on the screen.
-			if (m_drawedVerts.isEmpty())
-				drawBlip (paint, coordconv3_2 (m_hoverpos));
-			else
-			{
-				QVector<Vertex> verts, verts2;
-				const double dist0 = getCircleDrawDist (0),
-					dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1;
-				const int segs = g_lores;
-				const double angleUnit = (2 * pi) / segs;
-				Axis relX, relY;
-				QVector<QPoint> ringpoints, circlepoints, circle2points;
-
-				getRelativeAxes (relX, relY);
-
-				// Calculate the preview positions of vertices
-				for (int i = 0; i < segs; ++i)
-				{
-					Vertex v = g_origin;
-					v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0);
-					v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0);
-					verts << v;
-
-					if (dist1 != -1)
-					{
-						v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1);
-						v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist1);
-						verts2 << v;
-					}
-				}
-
-				int i = 0;
-				for (const Vertex& v : verts + verts2)
-				{
-					// Calculate the 2D point of the vertex
-					QPoint point = coordconv3_2 (v);
-
-					// Draw a green blip at where it is
-					drawBlip (paint, point);
-
-					// Add it to the list of points for the green ring fill.
-					ringpoints << point;
-
-					// Also add the circle points to separate lists
-					if (i < verts.size())
-						circlepoints << point;
-					else
-						circle2points << point;
-
-					++i;
-				}
-
-				// Insert the first point as the seventeenth one so that
-				// the ring polygon is closed properly.
-				if (ringpoints.size() >= 16)
-					ringpoints.insert (16, ringpoints[0]);
-
-				// Same for the outer ring. Note that the indices are offset by 1
-				// because of the insertion done above bumps the values.
-				if (ringpoints.size() >= 33)
-					ringpoints.insert (33, ringpoints[17]);
-
-				// Draw the ring
-				paint.setBrush ((m_drawedVerts.size() >= 2) ? polybrush : Qt::NoBrush);
-				paint.setPen (Qt::NoPen);
-				paint.drawPolygon (QPolygon (ringpoints));
-
-				// Draw the circles
-				paint.setBrush (Qt::NoBrush);
-				paint.setPen (linepen);
-				paint.drawPolygon (QPolygon (circlepoints));
-				paint.drawPolygon (QPolygon (circle2points));
-
-				{ // Draw the current radius in the middle of the circle.
-					QPoint origin = coordconv3_2 (m_drawedVerts[0]);
-					QString label = QString::number (dist0);
-					paint.setPen (textpen);
-					paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label);
-
-					if (m_drawedVerts.size() >= 2)
-					{
-						label = QString::number (dist1);
-						paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y() + metrics.height(), label);
-					}
-				}
-			}
-		}
-	}
-
-	// Camera icons
-	if (!isPicking())
-	{
-		// Draw a background for the selected camera
-		paint.setPen (m_thinBorderPen);
-		paint.setBrush (QBrush (QColor (0, 128, 160, 128)));
-		paint.drawRect (m_cameraIcons[camera()].selRect);
-
-		// Draw the actual icons
-		for (CameraIcon& info : m_cameraIcons)
-		{
-			// Don't draw the free camera icon when in draw mode
-			if (&info == &m_cameraIcons[GL::EFreeCamera] && editMode() != ESelectMode)
-				continue;
-
-			paint.drawPixmap (info.destRect, *info.img, info.srcRect);
-		}
-
-		QString formatstr = tr ("%1 Camera");
-
-		// Draw a label for the current camera in the bottom left corner
-		{
-			const int margin = 4;
-
-			QString label;
-			label = format (formatstr, tr (g_CameraNames[camera()]));
-			paint.setPen (textpen);
-			paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label);
-		}
-
-		// Tool tips
-		if (m_drawToolTip)
-		{
-			if (m_cameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false)
-				m_drawToolTip = false;
-			else
-			{
-				QString label = format (formatstr, tr (g_CameraNames[m_toolTipCamera]));
-				QToolTip::showText (m_globalpos, label);
-			}
-		}
-	}
-
-	// Message log
-	if (messageLog())
-	{
-		int y = 0;
-		const int margin = 2;
-		QColor penColor = textpen.color();
-
-		for (const MessageManager::Line& line : messageLog()->getLines())
-		{
-			penColor.setAlphaF (line.alpha);
-			paint.setPen (penColor);
-			paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text);
-			y += metrics.height();
-		}
-	}
-
-	// If we're range-picking, draw a rectangle encompassing the selection area.
-	if (m_rangepick && !isPicking() && m_totalmove >= 10)
-	{
-		int x0 = m_rangeStart.x(),
-			y0 = m_rangeStart.y(),
-			x1 = m_pos.x(),
-			y1 = m_pos.y();
-
-		QRect rect (x0, y0, x1 - x0, y1 - y0);
-		QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF");
-		fillColor.setAlphaF (0.2f);
-
-		paint.setPen (m_thickBorderPen);
-		paint.setBrush (QBrush (fillColor));
-		paint.drawRect (rect);
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::drawBlip (QPainter& paint, QPoint pos) const
-{
-	QPen pen = m_thinBorderPen;
-	const int blipsize = 8;
-	pen.setWidth (1);
-	paint.setPen (pen);
-	paint.setBrush (QColor (64, 192, 0));
-	paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize);
-}
-
-// =============================================================================
-//
-void GLRenderer::compileAllObjects()
-{
-	if (!document())
-		return;
-
-	// Compiling all is a big job, use a busy cursor
-	setCursor (Qt::BusyCursor);
-
-	m_knownVerts.clear();
-
-	for (LDObject* obj : document()->objects())
-		compileObject (obj);
-
-	// Compile axes
-	glDeleteLists (m_axeslist, 1);
-	m_axeslist = glGenLists (1);
-	glNewList (m_axeslist, GL_COMPILE);
-	glBegin (GL_LINES);
-
-	for (const LDGLAxis& ax : g_GLAxes)
-	{
-		qglColor (ax.col);
-		compileVertex (ax.vert);
-		compileVertex (-ax.vert);
-	}
-
-	glEnd();
-	glEndList();
-
-	setCursor (Qt::ArrowCursor);
-}
-
-// =============================================================================
-//
-void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype)
-{
-	glBegin (gltype);
-
-	const int numverts = (obj->type() != LDObject::ECondLine) ? obj->vertices() : 2;
-
-	if (g_glInvert == false)
-		for (int i = 0; i < numverts; ++i)
-			compileVertex (obj->vertex (i));
-	else
-		for (int i = numverts - 1; i >= 0; --i)
-			compileVertex (obj->vertex (i));
-
-	glEnd();
-}
-
-// =============================================================================
-//
-void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list)
-{
-	setObjectColor (obj, list);
-
-	switch (obj->type())
-	{
-		case LDObject::ELine:
-		{
-			compileSubObject (obj, GL_LINES);
-		} break;
-
-		case LDObject::ECondLine:
-		{
-			// Draw conditional lines with a dash pattern - however, use a full
-			// line when drawing a pick list to make selecting them easier.
-			if (list != GL::PickList)
-			{
-				glLineStipple (1, 0x6666);
-				glEnable (GL_LINE_STIPPLE);
-			}
-
-			compileSubObject (obj, GL_LINES);
-
-			glDisable (GL_LINE_STIPPLE);
-		} break;
-
-		case LDObject::ETriangle:
-		{
-			compileSubObject (obj, GL_TRIANGLES);
-		} break;
-
-		case LDObject::EQuad:
-		{
-			compileSubObject (obj, GL_QUADS);
-		} break;
-
-		case LDObject::ESubfile:
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDObjectList objs;
-
-			objs = ref->inlineContents (LDSubfile::DeepCacheInline | LDSubfile::RendererInline);
-			bool oldinvert = g_glInvert;
-
-			if (ref->transform().getDeterminant() < 0)
-				g_glInvert = !g_glInvert;
-
-			LDObject* prev = ref->previous();
-
-			if (prev && prev->type() == LDObject::EBFC && static_cast<LDBFC*> (prev)->statement() == LDBFC::InvertNext)
-				g_glInvert = !g_glInvert;
-
-			for (LDObject* obj : objs)
-			{
-				compileList (obj, list);
-				obj->destroy();
-			}
-
-			g_glInvert = oldinvert;
-		} break;
-
-		default:
-			break;
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::compileVertex (const Vertex& vrt)
-{
-	glVertex3d (vrt[X], -vrt[Y], -vrt[Z]);
-}
-
-// =============================================================================
-//
-void GLRenderer::clampAngle (double& angle) const
-{
-	while (angle < 0)
-		angle += 360.0;
-
-	while (angle > 360.0)
-		angle -= 360.0;
-}
-
-// =============================================================================
-//
-void GLRenderer::addDrawnVertex (Vertex pos)
-{
-	// If we picked an already-existing vertex, stop drawing
-	if (editMode() == EDrawMode)
-	{
-		for (Vertex& vert : m_drawedVerts)
-		{
-			if (vert == pos)
-			{
-				endDraw (true);
-				return;
-			}
-		}
-	}
-
-	m_drawedVerts << pos;
-}
-
-// =============================================================================
-//
-void GLRenderer::mouseReleaseEvent (QMouseEvent* ev)
-{
-	const bool wasLeft = (m_lastButtons & Qt::LeftButton) && ! (ev->buttons() & Qt::LeftButton),
-				   wasRight = (m_lastButtons & Qt::RightButton) && ! (ev->buttons() & Qt::RightButton),
-				   wasMid = (m_lastButtons & Qt::MidButton) && ! (ev->buttons() & Qt::MidButton);
-
-	if (m_panning)
-		m_panning = false;
-
-	if (wasLeft)
-	{
-		// Check if we selected a camera icon
-		if (!m_rangepick)
-		{
-			for (CameraIcon & info : m_cameraIcons)
-			{
-				if (info.destRect.contains (ev->pos()))
-				{
-					setCamera (info.cam);
-					goto end;
-				}
-			}
-		}
-
-		switch (editMode())
-		{
-			case EDrawMode:
-			{
-				if (m_rectdraw)
-				{
-					if (m_drawedVerts.size() == 2)
-					{
-						endDraw (true);
-						return;
-					}
-				} else
-				{
-					// If we have 4 verts, stop drawing.
-					if (m_drawedVerts.size() >= 4)
-					{
-						endDraw (true);
-						return;
-					}
-
-					if (m_drawedVerts.isEmpty() && ev->modifiers() & Qt::ShiftModifier)
-					{
-						m_rectdraw = true;
-						updateRectVerts();
-					}
-				}
-
-				addDrawnVertex (m_hoverpos);
-			} break;
-
-			case ECircleMode:
-			{
-				if (m_drawedVerts.size() == 3)
-				{
-					endDraw (true);
-					return;
-				}
-
-				addDrawnVertex (m_hoverpos);
-			} break;
-
-			case ESelectMode:
-			{
-				if (!isDrawOnly())
-				{
-					if (m_totalmove < 10)
-						m_rangepick = false;
-
-					if (!m_rangepick)
-						m_addpick = (m_keymods & Qt::ControlModifier);
-
-					if (m_totalmove < 10 || m_rangepick)
-						pick (ev->x(), ev->y());
-				}
-			} break;
-		}
-
-		m_rangepick = false;
-	}
-
-	if (wasMid && editMode() != ESelectMode && m_drawedVerts.size() < 4 && m_totalmove < 10)
-	{
-		// Find the closest vertex to our cursor
-		double mindist = 1024.0f;
-		Vertex closest;
-		bool valid = false;
-
-		QPoint curspos = coordconv3_2 (m_hoverpos);
-
-		for (const Vertex& pos3d: m_knownVerts)
-		{
-			QPoint pos2d = coordconv3_2 (pos3d);
-
-			// Measure squared distance
-			const double dx = abs (pos2d.x() - curspos.x()),
-						 dy = abs (pos2d.y() - curspos.y()),
-						 distsq = (dx * dx) + (dy * dy);
-
-			if (distsq >= 1024.0f) // 32.0f ** 2
-				continue; // too far away
-
-			if (distsq < mindist)
-			{
-				mindist = distsq;
-				closest = pos3d;
-				valid = true;
-
-				// If it's only 4 pixels away, I think we found our vertex now.
-				if (distsq <= 16.0f) // 4.0f ** 2
-					break;
-			}
-		}
-
-		if (valid)
-			addDrawnVertex (closest);
-	}
-
-	if (wasRight && !m_drawedVerts.isEmpty())
-	{
-		// Remove the last vertex
-		m_drawedVerts.removeLast();
-
-		if (m_drawedVerts.isEmpty())
-			m_rectdraw = false;
-	}
-
-end:
-	update();
-	m_totalmove = 0;
-}
-
-// =============================================================================
-//
-void GLRenderer::mousePressEvent (QMouseEvent* ev)
-{
-	m_totalmove = 0;
-
-	if (ev->modifiers() & Qt::ControlModifier)
-	{
-		m_rangepick = true;
-		m_rangeStart.setX (ev->x());
-		m_rangeStart.setY (ev->y());
-		m_addpick = (m_keymods & Qt::AltModifier);
-		ev->accept();
-	}
-
-	m_lastButtons = ev->buttons();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::mouseMoveEvent (QMouseEvent* ev)
-{
-	int dx = ev->x() - m_pos.x();
-	int dy = ev->y() - m_pos.y();
-	m_totalmove += abs (dx) + abs (dy);
-
-	const bool left = ev->buttons() & Qt::LeftButton,
-			   mid = ev->buttons() & Qt::MidButton,
-			   shift = ev->modifiers() & Qt::ShiftModifier;
-
-	if (mid || (left && shift))
-	{
-		pan (X) += 0.03f * dx * (zoom() / 7.5f);
-		pan (Y) -= 0.03f * dy * (zoom() / 7.5f);
-		m_panning = true;
-	} elif (left && !m_rangepick && camera() == EFreeCamera)
-	{
-		rot (X) = rot (X) + dy;
-		rot (Y) = rot (Y) + dx;
-
-		clampAngle (rot (X));
-		clampAngle (rot (Y));
-	}
-
-	// Start the tool tip timer
-	if (!m_drawToolTip)
-		m_toolTipTimer->start (500);
-
-	// Update 2d position
-	m_pos = ev->pos();
-	m_globalpos = ev->globalPos();
-
-	// Calculate 3d position of the cursor
-	m_hoverpos = (camera() != EFreeCamera) ? coordconv2_3 (m_pos, true) : g_origin;
-
-	// Update rect vertices since m_hoverpos may have changed
-	updateRectVerts();
-
-	update();
-}
-
-// =============================================================================
-//
-void GLRenderer::keyPressEvent (QKeyEvent* ev)
-{
-	m_keymods = ev->modifiers();
-}
-
-// =============================================================================
-//
-void GLRenderer::keyReleaseEvent (QKeyEvent* ev)
-{
-	m_keymods = ev->modifiers();
-}
-
-// =============================================================================
-//
-void GLRenderer::wheelEvent (QWheelEvent* ev)
-{
-	makeCurrent();
-
-	zoomNotch (ev->delta() > 0);
-	zoom() = clamp (zoom(), 0.01, 10000.0);
-
-	update();
-	ev->accept();
-}
-
-// =============================================================================
-//
-void GLRenderer::leaveEvent (QEvent* ev)
-{
-	(void) ev;
-	m_drawToolTip = false;
-	m_toolTipTimer->stop();
-	update();
-}
-
-// =============================================================================
-//
-void GLRenderer::contextMenuEvent (QContextMenuEvent* ev)
-{
-	g_win->spawnContextMenu (ev->globalPos());
-}
-
-// =============================================================================
-//
-void GLRenderer::setCamera (const GLRenderer::EFixedCamera cam)
-{
-	m_camera = cam;
-	gl_camera = (int) cam;
-	g_win->updateEditModeActions();
-}
-
-// =============================================================================
-//
-void GLRenderer::pick (int mouseX, int mouseY)
-{
-	makeCurrent();
-
-	// Use particularly thick lines while picking ease up selecting lines.
-	glLineWidth (max<double> (gl_linethickness, 6.5f));
-
-	// Clear the selection if we do not wish to add to it.
-	if (!m_addpick)
-	{
-		LDObjectList oldsel = selection();
-		getCurrentDocument()->clearSelection();
-
-		for (LDObject* obj : oldsel)
-			compileObject (obj);
-	}
-
-	setPicking (true);
-
-	// Paint the picking scene
-	glDisable (GL_DITHER);
-	glClearColor (1.0f, 1.0f, 1.0f, 1.0f);
-	drawGLScene();
-
-	int x0 = mouseX,
-		  y0 = mouseY;
-	int x1, y1;
-
-	// Determine how big an area to read - with range picking, we pick by
-	// the area given, with single pixel picking, we use an 1 x 1 area.
-	if (m_rangepick)
-	{
-		x1 = m_rangeStart.x();
-		y1 = m_rangeStart.y();
-	}
-	else
-	{
-		x1 = x0 + 1;
-		y1 = y0 + 1;
-	}
-
-	// x0 and y0 must be less than x1 and y1, respectively.
-	if (x0 > x1)
-		qSwap (x0, x1);
-
-	if (y0 > y1)
-		qSwap (y0, y1);
-
-	// Clamp the values to ensure they're within bounds
-	x0 = max (0, x0);
-	y0 = max (0, y0);
-	x1 = min (x1, m_width);
-	y1 = min (y1, m_height);
-	const int areawidth = (x1 - x0);
-	const int areaheight = (y1 - y0);
-	const qint32 numpixels = areawidth * areaheight;
-
-	// Allocate space for the pixel data.
-	uchar* const pixeldata = new uchar[4 * numpixels];
-	uchar* pixelptr = &pixeldata[0];
-
-	// Read pixels from the color buffer.
-	glReadPixels (x0, m_height - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata);
-
-	LDObject* removedObj = null;
-
-	// Go through each pixel read and add them to the selection.
-	for (qint32 i = 0; i < numpixels; ++i)
-	{
-		qint32 idx =
-			(*(pixelptr + 0) * 0x10000) +
-			(*(pixelptr + 1) * 0x00100) +
-			(*(pixelptr + 2) * 0x00001);
-		pixelptr += 4;
-
-		if (idx == 0xFFFFFF)
-			continue; // White is background; skip
-
-		LDObject* obj = LDObject::fromID (idx);
-		assert (obj != null);
-
-		// If this is an additive single pick and the object is currently selected,
-		// we remove it from selection instead.
-		if (!m_rangepick && m_addpick)
-		{
-			if (obj->isSelected())
-			{
-				obj->unselect();
-				removedObj = obj;
-				break;
-			}
-		}
-
-		obj->select();
-	}
-
-	delete[] pixeldata;
-
-	// Update everything now.
-	g_win->updateSelection();
-
-	// Recompile the objects now to update their color
-	for (LDObject* obj : selection())
-		compileObject (obj);
-
-	if (removedObj)
-		compileObject (removedObj);
-
-	// Restore line thickness
-	glLineWidth (gl_linethickness);
-
-	setPicking (false);
-	m_rangepick = false;
-	glEnable (GL_DITHER);
-
-	setBackground();
-	repaint();
-}
-
-// =============================================================================
-//
-void GLRenderer::setEditMode (EditMode const& a)
-{
-	m_editMode = a;
-
-	switch (a)
-	{
-		case ESelectMode:
-		{
-			unsetCursor();
-			setContextMenuPolicy (Qt::DefaultContextMenu);
-		} break;
-
-		case EDrawMode:
-		case ECircleMode:
-		{
-			// Cannot draw into the free camera - use top instead.
-			if (m_camera == EFreeCamera)
-				setCamera (ETopCamera);
-
-			// Disable the context menu - we need the right mouse button
-			// for removing vertices.
-			setContextMenuPolicy (Qt::NoContextMenu);
-
-			// Use the crosshair cursor when drawing.
-			setCursor (Qt::CrossCursor);
-
-			// Clear the selection when beginning to draw.
-			LDObjectList priorsel = selection();
-			getCurrentDocument()->clearSelection();
-
-			for (LDObject* obj : priorsel)
-				compileObject (obj);
-
-			g_win->updateSelection();
-			m_drawedVerts.clear();
-		} break;
-	}
-
-	g_win->updateEditModeActions();
-	update();
-}
-
-// =============================================================================
-//
-void GLRenderer::setDocument (LDDocument* const& a)
-{
-	m_document = a;
-
-	if (a != null)
-	{
-		initOverlaysFromObjects();
-
-		if (currentDocumentData().init == false)
-		{
-			resetAllAngles();
-			currentDocumentData().init = true;
-		}
-	}
-}
-
-// =============================================================================
-//
-Matrix GLRenderer::getCircleDrawMatrix (double scale)
-{
-	Matrix transform = g_circleDrawMatrixTemplates[camera() % 3];
-
-	for (int i = 0; i < 9; ++i)
-	{
-		if (transform[i] == 2)
-			transform[i] = scale;
-		elif (transform[i] == 1 && camera() >= 3)
-			transform[i] = -1;
-	}
-
-	return transform;
-}
-
-// =============================================================================
-//
-void GLRenderer::endDraw (bool accept)
-{
-	(void) accept;
-
-	// Clean the selection and create the object
-	QList<Vertex>& verts = m_drawedVerts;
-	LDObjectList objs;
-
-	switch (editMode())
-	{
-		case EDrawMode:
-		{
-			if (m_rectdraw)
-			{
-				LDQuad* quad = new LDQuad;
-
-				// Copy the vertices from m_rectverts
-				updateRectVerts();
-
-				for (int i = 0; i < quad->vertices(); ++i)
-					quad->setVertex (i, m_rectverts[i]);
-
-				quad->setColor (maincolor);
-				objs << quad;
-			}
-			else
-			{
-				switch (verts.size())
-				{
-					case 1:
-					{
-						// 1 vertex - add a vertex object
-						LDVertex* obj = new LDVertex;
-						obj->pos = verts[0];
-						obj->setColor (maincolor);
-						objs << obj;
-					} break;
-
-					case 2:
-					{
-						// 2 verts - make a line
-						LDLine* obj = new LDLine (verts[0], verts[1]);
-						obj->setColor (edgecolor);
-						objs << obj;
-					} break;
-
-					case 3:
-					case 4:
-					{
-						LDObject* obj = (verts.size() == 3) ?
-							  static_cast<LDObject*> (new LDTriangle) :
-							  static_cast<LDObject*> (new LDQuad);
-
-						obj->setColor (maincolor);
-
-						for (int i = 0; i < obj->vertices(); ++i)
-							obj->setVertex (i, verts[i]);
-
-						objs << obj;
-					} break;
-				}
-			}
-		} break;
-
-		case ECircleMode:
-		{
-			const int segs = g_lores, divs = g_lores; // TODO: make customizable
-			double dist0 = getCircleDrawDist (0),
-				dist1 = getCircleDrawDist (1);
-			LDDocument* refFile = null;
-			Matrix transform;
-			bool circleOrDisc = false;
-
-			if (dist1 < dist0)
-				std::swap<double> (dist0, dist1);
-
-			if (dist0 == dist1)
-			{
-				// If the radii are the same, there's no ring space to fill. Use a circle.
-				refFile = ::getDocument ("4-4edge.dat");
-				transform = getCircleDrawMatrix (dist0);
-				circleOrDisc = true;
-			}
-			elif (dist0 == 0 || dist1 == 0)
-			{
-				// If either radii is 0, use a disc.
-				refFile = ::getDocument ("4-4disc.dat");
-				transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1);
-				circleOrDisc = true;
-			}
-			elif (g_RingFinder.findRings (dist0, dist1))
-			{
-				// The ring finder found a solution, use that. Add the component rings to the file.
-				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 = ::getDocument (radialFileName (::Ring, g_lores, g_lores, cmp.num))) == null)
-					{
-						refFile = generatePrimitive (::Ring, g_lores, g_lores, cmp.num);
-						refFile->setImplicit (false);
-					}
-
-					LDSubfile* ref = new LDSubfile;
-					ref->setFileInfo (refFile);
-					ref->setTransform (getCircleDrawMatrix (cmp.scale));
-					ref->setPosition (m_drawedVerts[0]);
-					ref->setColor (maincolor);
-					objs << ref;
-				}
-			}
-			else
-			{
-				// Ring finder failed, last resort: draw the ring with quads
-				QList<QLineF> c0, c1;
-				Axis relX, relY, relZ;
-				getRelativeAxes (relX, relY);
-				relZ = (Axis) (3 - relX - relY);
-				double x0 = m_drawedVerts[0][relX],
-					y0 = m_drawedVerts[0][relY];
-
-				Vertex templ;
-				templ[relX] = x0;
-				templ[relY] = y0;
-				templ[relZ] = getDepthValue();
-
-				// Calculate circle coords
-				makeCircle (segs, divs, dist0, c0);
-				makeCircle (segs, divs, dist1, c1);
-
-				for (int i = 0; i < segs; ++i)
-				{
-					Vertex v0, v1, v2, v3;
-					v0 = v1 = v2 = v3 = templ;
-					v0[relX] += c0[i].x1();
-					v0[relY] += c0[i].y1();
-					v1[relX] += c0[i].x2();
-					v1[relY] += c0[i].y2();
-					v2[relX] += c1[i].x2();
-					v2[relY] += c1[i].y2();
-					v3[relX] += c1[i].x1();
-					v3[relY] += c1[i].y1();
-
-					LDQuad* q = new LDQuad (v0, v1, v2, v3);
-					q->setColor (maincolor);
-
-					// Ensure the quads always are BFC-front towards the camera
-					if (camera() % 3 <= 0)
-						q->invert();
-
-					objs << q;
-				}
-			}
-
-			if (circleOrDisc)
-			{
-				LDSubfile* ref = new LDSubfile;
-				ref->setFileInfo (refFile);
-				ref->setTransform (transform);
-				ref->setPosition (m_drawedVerts[0]);
-				ref->setColor (maincolor);
-				objs << ref;
-			}
-		} break;
-
-		case ESelectMode:
-		{
-			// this shouldn't happen
-			assert (false);
-			return;
-		} break;
-	}
-
-	if (objs.size() > 0)
-	{
-		for (LDObject* obj : objs)
-		{
-			document()->addObject (obj);
-			compileObject (obj);
-		}
-
-		g_win->refresh();
-		g_win->endAction();
-	}
-
-	m_drawedVerts.clear();
-	m_rectdraw = false;
-}
-
-// =============================================================================
-//
-double GLRenderer::getCircleDrawDist (int pos) const
-{
-	assert (m_drawedVerts.size() >= pos + 1);
-	const Vertex& v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : m_hoverpos;
-	Axis relX, relY;
-	getRelativeAxes (relX, relY);
-
-	const double dx = m_drawedVerts[0][relX] - v1[relX];
-	const double dy = m_drawedVerts[0][relY] - v1[relY];
-	return sqrt ((dx * dx) + (dy * dy));
-}
-
-// =============================================================================
-//
-void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const
-{
-	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
-	relX = cam->axisX;
-	relY = cam->axisY;
-}
-
-// =============================================================================
-//
-static QList<Vertex> getVertices (LDObject* obj)
-{
-	QList<Vertex> verts;
-
-	if (obj->vertices() >= 2)
-	{
-		for (int i = 0; i < obj->vertices(); ++i)
-			verts << obj->vertex (i);
-	} elif (obj->type() == LDObject::ESubfile)
-	{
-		LDSubfile* ref = static_cast<LDSubfile*> (obj);
-		LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline);
-
-		for (LDObject* obj : objs)
-		{
-			verts << getVertices (obj);
-			obj->destroy();
-		}
-	}
-
-	return verts;
-}
-
-// =============================================================================
-//
-void GLRenderer::compileObject (LDObject* obj)
-{
-	deleteLists (obj);
-
-	for (const GL::ListType listType : g_glListTypes)
-	{
-		if (isDrawOnly() && listType != GL::NormalList)
-			continue;
-
-		GLuint list = glGenLists (1);
-		glNewList (list, GL_COMPILE);
-
-		obj->glLists[listType] = list;
-		compileList (obj, listType);
-
-		glEndList();
-	}
-
-	// Mark in known vertices of this object
-	QList<Vertex> verts = getVertices (obj);
-	m_knownVerts << verts;
-	removeDuplicates (m_knownVerts);
-
-	obj->setGLInit (true);
-}
-
-// =============================================================================
-//
-uchar* GLRenderer::getScreencap (int& w, int& h)
-{
-	w = m_width;
-	h = m_height;
-	uchar* cap = new uchar[4 * w * h];
-
-	m_screencap = true;
-	update();
-	m_screencap = false;
-
-	// Capture the pixels
-	glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap);
-
-	return cap;
-}
-
-// =============================================================================
-//
-void GLRenderer::slot_toolTipTimer()
-{
-	// We come here if the cursor has stayed in one place for longer than a
-	// a second. Check if we're holding it over a camera icon - if so, draw
-	// a tooltip.
-for (CameraIcon & icon : m_cameraIcons)
-	{
-		if (icon.destRect.contains (m_pos))
-		{
-			m_toolTipCamera = icon.cam;
-			m_drawToolTip = true;
-			update();
-			break;
-		}
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::deleteLists (LDObject* obj)
-{
-	// Delete the lists but only if they have been initialized
-	if (!obj->isGLInit())
-		return;
-
-	for (const GL::ListType listType : g_glListTypes)
-		glDeleteLists (obj->glLists[listType], 1);
-
-	obj->setGLInit (false);
-}
-
-// =============================================================================
-//
-Axis GLRenderer::getCameraAxis (bool y, GLRenderer::EFixedCamera camid)
-{
-	if (camid == (GL::EFixedCamera) - 1)
-		camid = m_camera;
-
-	const LDFixedCameraInfo* cam = &g_FixedCameras[camid];
-	return (y) ? cam->axisY : cam->axisX;
-}
-
-// =============================================================================
-//
-bool GLRenderer::setupOverlay (EFixedCamera cam, QString file, int x, int y, int w, int h)
-{
-	QImage* img = new QImage (QImage (file).convertToFormat (QImage::Format_ARGB32));
-	LDGLOverlay& info = getOverlay (cam);
-
-	if (img->isNull())
-	{
-		critical (tr ("Failed to load overlay image!"));
-		delete img;
-		return false;
-	}
-
-	delete info.img; // delete the old image
-
-	info.fname = file;
-	info.lw = w;
-	info.lh = h;
-	info.ox = x;
-	info.oy = y;
-	info.img = img;
-
-	if (info.lw == 0)
-		info.lw = (info.lh * img->width()) / img->height();
-	elif (info.lh == 0)
-		info.lh = (info.lw * img->height()) / img->width();
-
-	const Axis x2d = getCameraAxis (false, cam),
-		y2d = getCameraAxis (true, cam);
-	const double negXFac = g_FixedCameras[cam].negX ? -1 : 1,
-		negYFac = g_FixedCameras[cam].negY ? -1 : 1;
-
-	info.v0 = info.v1 = g_origin;
-	info.v0[x2d] = - (info.ox * info.lw * negXFac) / img->width();
-	info.v0[y2d] = (info.oy * info.lh * negYFac) / img->height();
-	info.v1[x2d] = info.v0[x2d] + info.lw;
-	info.v1[y2d] = info.v0[y2d] + info.lh;
-
-	// Set alpha of all pixels to 0.5
-	for (long i = 0; i < img->width(); ++i)
-		for (long j = 0; j < img->height(); ++j)
-		{
-			uint32 pixel = img->pixel (i, j);
-			img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF));
-		}
-
-	updateOverlayObjects();
-	return true;
-}
-
-// =============================================================================
-//
-void GLRenderer::clearOverlay()
-{
-	if (camera() == EFreeCamera)
-		return;
-
-	LDGLOverlay& info = currentDocumentData().overlays[camera()];
-	delete info.img;
-	info.img = null;
-
-	updateOverlayObjects();
-}
-
-// =============================================================================
-//
-void GLRenderer::setDepthValue (double depth)
-{
-	assert (camera() < EFreeCamera);
-	currentDocumentData().depthValues[camera()] = depth;
-}
-
-// =============================================================================
-//
-double GLRenderer::getDepthValue() const
-{
-	assert (camera() < EFreeCamera);
-	return currentDocumentData().depthValues[camera()];
-}
-
-// =============================================================================
-//
-const char* GLRenderer::getCameraName() const
-{
-	return g_CameraNames[camera()];
-}
-
-// =============================================================================
-//
-LDGLOverlay& GLRenderer::getOverlay (int newcam)
-{
-	return currentDocumentData().overlays[newcam];
-}
-
-// =============================================================================
-//
-void GLRenderer::zoomNotch (bool inward)
-{
-	if (zoom() > 15)
-		zoom() *= inward ? 0.833f : 1.2f;
-	else
-		zoom() += inward ? -1.2f : 1.2f;
-}
-
-// =============================================================================
-//
-void GLRenderer::zoomToFit()
-{
-	if (document() == null || m_width == -1 || m_height == -1)
-	{
-		zoom() = 30.0f;
-		return;
-	}
-
-	bool lastfilled = false;
-	bool firstrun = true;
-	const uint32 white = 0xFFFFFFFF;
-	bool inward = true;
-	const int w = m_width, h = m_height;
-	int runaway = 50;
-
-	glClearColor (1.0, 1.0, 1.0, 1.0);
-	glDisable (GL_DITHER);
-
-	// Use the pick list while drawing the scene, this way we can tell whether borders
-	// are background or not.
-	setPicking (true);
-
-	while (--runaway)
-	{
-		if (zoom() > 10000.0 || zoom() < 0.0)
-		{
-			// Obviously, there's nothing to draw if we get here.
-			// Default to 30.0f and break out.
-			zoom() = 30.0;
-			break;
-		}
-
-		zoomNotch (inward);
-
-		uchar* cap = new uchar[4 * w * h];
-		drawGLScene();
-		glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap);
-		uint32* imgdata = reinterpret_cast<uint32*> (cap);
-		bool filled = false;
-
-		// Check the top and bottom rows
-		for (int i = 0; i < w; ++i)
-		{
-			if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white)
-			{
-				filled = true;
-				goto endOfLoop;
-			}
-		}
-
-		// Left and right edges
-		for (int i = 0; i < h; ++i)
-		{
-			if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white)
-			{
-				filled = true;
-				goto endOfLoop;
-			}
-		}
-
-endOfLoop:
-
-		delete[] cap;
-
-		if (firstrun)
-		{
-			// If this is the first run, we don't know enough to determine
-			// whether the zoom was to fit, so we mark in our knowledge so
-			// far and start over.
-			inward = !filled;
-			firstrun = false;
-		}
-		else
-		{
-			// If this run filled the screen and the last one did not, the
-			// last run had ideal zoom - zoom a bit back and we should reach it.
-			if (filled && !lastfilled)
-			{
-				zoomNotch (false);
-				break;
-			}
-
-			// If this run did not fill the screen and the last one did, we've
-			// now reached ideal zoom so we're done here.
-			if (!filled && lastfilled)
-				break;
-
-			inward = !filled;
-		}
-
-		lastfilled = filled;
-	}
-
-	setBackground();
-	setPicking (false);
-}
-
-// =============================================================================
-//
-void GLRenderer::zoomAllToFit()
-{
-	EFixedCamera oldcam = camera();
-
-	for (int i = 0; i < 7; ++i)
-	{
-		setCamera ((EFixedCamera) i);
-		zoomToFit();
-	}
-
-	setCamera (oldcam);
-}
-
-// =============================================================================
-//
-void GLRenderer::updateRectVerts()
-{
-	if (!m_rectdraw)
-		return;
-
-	if (m_drawedVerts.isEmpty())
-	{
-		for (int i = 0; i < 4; ++i)
-			m_rectverts[i] = m_hoverpos;
-
-		return;
-	}
-
-	Vertex v0 = m_drawedVerts[0],
-		   v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : m_hoverpos;
-
-	const Axis ax = getCameraAxis (false),
-			   ay = getCameraAxis (true),
-			   az = (Axis) (3 - ax - ay);
-
-	for (int i = 0; i < 4; ++i)
-		m_rectverts[i][az] = getDepthValue();
-
-	m_rectverts[0][ax] = v0[ax];
-	m_rectverts[0][ay] = v0[ay];
-	m_rectverts[1][ax] = v1[ax];
-	m_rectverts[1][ay] = v0[ay];
-	m_rectverts[2][ax] = v1[ax];
-	m_rectverts[2][ay] = v1[ay];
-	m_rectverts[3][ax] = v0[ax];
-	m_rectverts[3][ay] = v1[ay];
-}
-
-// =============================================================================
-//
-void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev)
-{
-	if (!(ev->buttons() & Qt::LeftButton) || editMode() != ESelectMode)
-		return;
-
-	pick (ev->x(), ev->y());
-
-	if (selection().isEmpty())
-		return;
-
-	LDObject* obj = selection().first();
-	AddObjectDialog::staticDialog (obj->type(), obj);
-	g_win->endAction();
-	ev->accept();
-}
-
-// =============================================================================
-//
-LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam)
-{
-	LDOverlay* ovlobj = null;
-
-	for (LDObject* obj : document()->objects())
-	{
-		if (obj->type() == LDObject::EOverlay && static_cast<LDOverlay*> (obj)->camera() == cam)
-		{
-			ovlobj = static_cast<LDOverlay*> (obj);
-			break;
-		}
-	}
-
-	return ovlobj;
-}
-
-// =============================================================================
-//
-// Read in overlays from the current file and update overlay info accordingly.
-//
-void GLRenderer::initOverlaysFromObjects()
-{
-	for (EFixedCamera cam : g_Cameras)
-	{
-		if (cam == EFreeCamera)
-			continue;
-
-		LDGLOverlay& meta = currentDocumentData().overlays[cam];
-		LDOverlay* ovlobj = findOverlayObject (cam);
-
-		if (!ovlobj && meta.img)
-		{
-			delete meta.img;
-			meta.img = null;
-		}
-		elif (ovlobj && (!meta.img || meta.fname != ovlobj->fileName()))
-			setupOverlay (cam, ovlobj->fileName(), ovlobj->x(),
-				ovlobj->y(), ovlobj->width(), ovlobj->height());
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::updateOverlayObjects()
-{
-	for (EFixedCamera cam : g_Cameras)
-	{
-		if (cam == EFreeCamera)
-			continue;
-
-		LDGLOverlay& meta = currentDocumentData().overlays[cam];
-		LDOverlay* ovlobj = findOverlayObject (cam);
-
-		if (!meta.img && ovlobj)
-		{
-			// If this is the last overlay image, we need to remove the empty space after it as well.
-			LDObject* nextobj = ovlobj->next();
-
-			if (nextobj && nextobj->type() == LDObject::EEmpty)
-				nextobj->destroy();
-
-			// If the overlay object was there and the overlay itself is
-			// not, remove the object.
-			ovlobj->destroy();
-		} elif (meta.img && !ovlobj)
-		{
-			// Inverse case: image is there but the overlay object is
-			// not, thus create the object.
-			ovlobj = new LDOverlay;
-
-			// Find a suitable position to place this object. We want to place
-			// this into the header, which is everything up to the first scemantic
-			// object. If we find another overlay object, place this object after
-			// the last one found. Otherwise, place it before the first schemantic
-			// object and put an empty object after it (though don't do this if
-			// there was no schemantic elements at all)
-			int i, lastOverlay = -1;
-			bool found = false;
-
-			for (i = 0; i < document()->getObjectCount(); ++i)
-			{
-				LDObject* obj = document()->getObject (i);
-
-				if (obj->isScemantic())
-				{
-					found = true;
-					break;
-				}
-
-				if (obj->type() == LDObject::EOverlay)
-					lastOverlay = i;
-			}
-
-			if (lastOverlay != -1)
-				document()->insertObj (lastOverlay + 1, ovlobj);
-			else
-			{
-				document()->insertObj (i, ovlobj);
-
-				if (found)
-					document()->insertObj (i + 1, new LDEmpty);
-			}
-		}
-
-		if (meta.img && ovlobj)
-		{
-			ovlobj->setCamera (cam);
-			ovlobj->setFileName (meta.fname);
-			ovlobj->setX (meta.ox);
-			ovlobj->setY (meta.oy);
-			ovlobj->setWidth (meta.lw);
-			ovlobj->setHeight (meta.lh);
-		}
-	}
-
-	if (g_win->R() == this)
-		g_win->refresh();
-}
--- a/src/GLRenderer.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,311 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QGLWidget>
-#include "Main.h"
-#include "Macros.h"
-#include "LDObject.h"
-#include "Document.h"
-
-class MessageManager;
-class QDialogButtonBox;
-class RadioGroup;
-class QDoubleSpinBox;
-class QSpinBox;
-class QLineEdit;
-class QTimer;
-
-enum EditMode
-{
-	ESelectMode,
-	EDrawMode,
-	ECircleMode,
-};
-
-// Meta for overlays
-struct LDGLOverlay
-{
-	Vertex			v0,
-					v1;
-	int				ox,
-					oy;
-	double			lw,
-					lh;
-	QString			fname;
-	QImage*			img;
-};
-
-struct LDFixedCameraInfo
-{
-	const char		glrotate[3];
-	const Axis		axisX,
-					axisY;
-	const bool		negX,
-					negY;
-};
-
-// =============================================================================
-// Document-specific data
-//
-struct LDGLData
-{
-	double			rotX,
-					rotY,
-					rotZ,
-					panX[7],
-					panY[7],
-					zoom[7];
-	double			depthValues[6];
-	LDGLOverlay		overlays[6];
-	bool			init;
-
-	LDGLData()
-	{
-		for (int i = 0; i < 6; ++i)
-		{
-			overlays[i].img = null;
-			depthValues[i] = 0.0f;
-		}
-
-		init = false;
-	}
-};
-
-// =============================================================================
-// The main renderer object, draws the brick on the screen, manages the camera
-// and selection picking. The instance of GLRenderer is accessible as
-// g_win->R()
-//
-class GLRenderer : public QGLWidget
-{
-	public:
-		enum EFixedCamera
-		{
-			ETopCamera,
-			EFrontCamera,
-			ELeftCamera,
-			EBottomCamera,
-			EBackCamera,
-			ERightCamera,
-			EFreeCamera
-		};
-
-		enum ListType
-		{
-			NormalList,
-			PickList,
-			BFCFrontList,
-			BFCBackList
-		};
-
-		// CameraIcon::img is a heap-allocated QPixmap because otherwise it gets
-		// initialized before program gets to main() and constructs a QApplication
-		// and Qt doesn't like that.
-		struct CameraIcon
-		{
-			QPixmap*			img;
-			QRect				srcRect,
-								destRect,
-								selRect;
-			EFixedCamera	cam;
-		};
-
-		Q_OBJECT
-		PROPERTY (public,	bool,				isDrawOnly,	setDrawOnly,	STOCK_WRITE)
-		PROPERTY (public,	MessageManager*,	messageLog, setMessageLog,	STOCK_WRITE)
-		PROPERTY (private,	bool,				isPicking,	setPicking,		STOCK_WRITE)
-		PROPERTY (public,	LDDocument*,		document,	setDocument,	CUSTOM_WRITE)
-		PROPERTY (public,	EditMode,			editMode,	setEditMode,	CUSTOM_WRITE)
-
-	public:
-		GLRenderer (QWidget* parent = null);
-		~GLRenderer();
-
-		inline EFixedCamera camera() const
-		{
-			return m_camera;
-		}
-
-		void           clearOverlay();
-		void           compileObject (LDObject* obj);
-		void           compileAllObjects();
-		void           drawGLScene();
-		void           endDraw (bool accept);
-		Axis           getCameraAxis (bool y, EFixedCamera camid = (EFixedCamera) - 1);
-		const char*    getCameraName() const;
-		double         getDepthValue() const;
-		QColor         getMainColor();
-		LDGLOverlay&   getOverlay (int newcam);
-		uchar*         getScreencap (int& w, int& h);
-		void           hardRefresh();
-		void           initGLData();
-		void           initOverlaysFromObjects();
-		void           refresh();
-		void           resetAngles();
-		void           resetAllAngles();
-		void           setBackground();
-		void           setCamera (const EFixedCamera cam);
-		void           setDepthValue (double depth);
-		bool           setupOverlay (EFixedCamera cam, QString file, int x, int y, int w, int h);
-		void           updateOverlayObjects();
-		void           zoomNotch (bool inward);
-		void           zoomToFit();
-		void           zoomAllToFit();
-
-		static void    deleteLists (LDObject* obj);
-
-	protected:
-		void           contextMenuEvent (QContextMenuEvent* ev);
-		void           initializeGL();
-		void           keyPressEvent (QKeyEvent* ev);
-		void           keyReleaseEvent (QKeyEvent* ev);
-		void           leaveEvent (QEvent* ev);
-		void           mouseDoubleClickEvent (QMouseEvent* ev);
-		void           mousePressEvent (QMouseEvent* ev);
-		void           mouseMoveEvent (QMouseEvent* ev);
-		void           mouseReleaseEvent (QMouseEvent* ev);
-		void           paintEvent (QPaintEvent* ev);
-		void           resizeGL (int w, int h);
-		void           wheelEvent (QWheelEvent* ev);
-
-	private:
-		CameraIcon					m_cameraIcons[7];
-		QTimer*						m_toolTipTimer;
-		Qt::MouseButtons			m_lastButtons;
-		Qt::KeyboardModifiers		m_keymods;
-		Vertex						m_hoverpos;
-		double						m_virtWidth,
-									m_virtHeight;
-		bool						m_darkbg,
-									m_rangepick,
-									m_addpick,
-									m_drawToolTip,
-									m_screencap,
-									m_panning;
-		QPoint						m_pos,
-									m_globalpos,
-									m_rangeStart;
-		QPen						m_thickBorderPen,
-									m_thinBorderPen;
-		EFixedCamera				m_camera,
-									m_toolTipCamera;
-		GLuint						m_axeslist;
-		int							m_width,
-									m_height,
-									m_totalmove;
-		QList<Vertex>				m_drawedVerts;
-		bool						m_rectdraw;
-		Vertex						m_rectverts[4];
-		QColor						m_bgcolor;
-		QList<Vertex>				m_knownVerts;
-
-		void           addDrawnVertex (Vertex m_hoverpos);
-		LDOverlay*     findOverlayObject (EFixedCamera cam);
-		void           updateRectVerts();
-		void           getRelativeAxes (Axis& relX, Axis& relY) const;
-		Matrix         getCircleDrawMatrix (double scale);
-		void           drawBlip (QPainter& paint, QPoint pos) const;
-
-		// Compute geometry for camera icons
-		void           calcCameraIcons();
-
-		// How large is the circle we're drawing right now?
-		double         getCircleDrawDist (int pos) const;
-
-		// Clamps an angle to [0, 360]
-		void           clampAngle (double& angle) const;
-
-		// Compile one of the lists of an object
-		void           compileList (LDObject* obj, const ListType list);
-
-		// Sub-routine for object compiling
-		void           compileSubObject (LDObject* obj, const GLenum gltype);
-
-		// Compile a single vertex to a list
-		void           compileVertex (const Vertex& vrt);
-
-		// Convert a 2D point to a 3D point
-		Vertex         coordconv2_3 (const QPoint& pos2d, bool snap) const;
-
-		// Convert a 3D point to a 2D point
-		QPoint         coordconv3_2 (const Vertex& pos3d) const;
-
-		// Perform object selection
-		void           pick (int mouseX, int mouseY);
-
-		// Set the color to an object list
-		void           setObjectColor (LDObject* obj, const ListType list);
-
-		LDGLData& currentDocumentData() const
-		{
-			return *document()->getGLData();
-		}
-
-		// Get a rotation value
-		inline double& rot (Axis ax)
-		{
-			return
-				(ax == X) ? currentDocumentData().rotX :
-				(ax == Y) ? currentDocumentData().rotY :
-				            currentDocumentData().rotZ;
-		}
-
-		// Get a panning value
-		inline double& pan (Axis ax)
-		{
-			return (ax == X) ? currentDocumentData().panX[camera()] :
-				currentDocumentData().panY[camera()];
-		}
-
-		// Same except const (can be used in const methods)
-		inline const double& pan (Axis ax) const
-		{
-			return (ax == X) ? currentDocumentData().panX[camera()] :
-				currentDocumentData().panY[camera()];
-		}
-
-		// Get the zoom value
-		inline double& zoom()
-		{
-			return currentDocumentData().zoom[camera()];
-		}
-
-		template<typename... Args>
-		inline QString format (QString fmtstr, Args... args)
-		{
-			return ::format (fmtstr, args...);
-		}
-
-	private slots:
-		void           slot_toolTipTimer();
-};
-
-// Alias for short namespaces
-typedef GLRenderer GL;
-
-static const GLRenderer::ListType g_glListTypes[] =
-{
-	GL::NormalList,
-	GL::PickList,
-	GL::BFCFrontList,
-	GL::BFCBackList,
-};
-
-extern const GL::EFixedCamera g_Cameras[7];
-extern const char* g_CameraNames[7];
--- a/src/LDConfig.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,215 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QFile>
-#include "Document.h"
-#include "LDConfig.h"
-#include "MainWindow.h"
-#include "Misc.h"
-#include "Colors.h"
-
-// =============================================================================
-//
-// Helper function for parseLDConfig
-//
-static bool parseLDConfigTag (LDConfigParser& pars, char const* tag, QString& val)
-{
-	int pos;
-
-	// Try find the token and get its position
-	if (!pars.findToken (pos, tag, 1))
-		return false;
-
-	// Get the token after it and store it into val
-	return pars.getToken (val, pos + 1);
-}
-
-// =============================================================================
-//
-void parseLDConfig()
-{
-	QFile* fp = openLDrawFile ("LDConfig.ldr", false);
-
-	if (!fp)
-	{
-		critical (QObject::tr ("Unable to open LDConfig.ldr for parsing."));
-		return;
-	}
-
-	// Read in the lines
-	while (fp->atEnd() == false)
-	{
-		QString line = QString::fromUtf8 (fp->readLine());
-
-		if (line.isEmpty() || line[0] != '0')
-			continue; // empty or illogical
-
-		line.remove ('\r');
-		line.remove ('\n');
-
-		// Parse the line
-		LDConfigParser pars (line, ' ');
-
-		int code = 0, alpha = 255;
-		QString name, facename, edgename, valuestr;
-
-		// Check 0 !COLOUR, parse the name
-		if (!pars.tokenCompare (0, "0") || !pars.tokenCompare (1, "!COLOUR") || !pars.getToken (name, 2))
-			continue;
-
-		// Replace underscores in the name with spaces for readability
-		name.replace ("_", " ");
-
-		// Get the CODE tag
-		if (!parseLDConfigTag (pars, "CODE", valuestr))
-			continue;
-
-		if (!numeric (valuestr))
-			continue; // not a number
-
-		// Ensure that the code is within [0 - 511]
-		bool ok;
-		code = valuestr.toShort (&ok);
-
-		if (!ok || code < 0 || code >= 512)
-			continue;
-
-		// VALUE and EDGE tags
-		if (!parseLDConfigTag (pars, "VALUE", facename) || !parseLDConfigTag (pars, "EDGE", edgename))
-			continue;
-
-		// Ensure that our colors are correct
-		QColor faceColor (facename),
-			edgeColor (edgename);
-
-		if (!faceColor.isValid() || !edgeColor.isValid())
-			continue;
-
-		// Parse alpha if given.
-		if (parseLDConfigTag (pars, "ALPHA", valuestr))
-			alpha = clamp (valuestr.toInt(), 0, 255);
-
-		LDColor* col = new LDColor;
-		col->name = name;
-		col->faceColor = faceColor;
-		col->edgeColor = edgeColor;
-		col->hexcode = facename;
-		col->faceColor.setAlpha (alpha);
-		col->index = code;
-		setColor (code, col);
-	}
-
-	fp->close();
-	fp->deleteLater();
-}
-
-// =============================================================================
-//
-LDConfigParser::LDConfigParser (QString inText, char sep)
-{
-	m_tokens = inText.split (sep, QString::SkipEmptyParts);
-	m_pos = -1;
-}
-
-// =============================================================================
-//
-bool LDConfigParser::isAtBeginning()
-{
-	return m_pos == -1;
-}
-
-// =============================================================================
-//
-bool LDConfigParser::isAtEnd()
-{
-	return m_pos == m_tokens.size() - 1;
-}
-
-// =============================================================================
-//
-bool LDConfigParser::getToken (QString& val, const int pos)
-{
-	if (pos >= m_tokens.size())
-		return false;
-
-	val = m_tokens[pos];
-	return true;
-}
-
-// =============================================================================
-//
-bool LDConfigParser::getNextToken (QString& val)
-{
-	return getToken (val, ++m_pos);
-}
-
-// =============================================================================
-//
-bool LDConfigParser::peekNextToken (QString& val)
-{
-	return getToken (val, m_pos + 1);
-}
-
-// =============================================================================
-//
-bool LDConfigParser::findToken (int& result, char const* needle, int args)
-{
-	for (int i = 0; i < (m_tokens.size() - args); ++i)
-	{
-		if (m_tokens[i] == needle)
-		{
-			result = i;
-			return true;
-		}
-	}
-
-	return false;
-}
-
-// =============================================================================
-//
-void LDConfigParser::rewind()
-{
-	m_pos = -1;
-}
-
-// =============================================================================
-//
-void LDConfigParser::seek (int amount, bool rel)
-{
-	m_pos = (rel ? m_pos : 0) + amount;
-}
-
-// =============================================================================
-//
-int LDConfigParser::getSize()
-{
-	return m_tokens.size();
-}
-
-// =============================================================================
-//
-bool LDConfigParser::tokenCompare (int inPos, const char* sOther)
-{
-	QString tok;
-
-	if (!getToken (tok, inPos))
-		return false;
-
-	return (tok == sOther);
-}
--- a/src/LDConfig.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include "Types.h"
-#include <QStringList>
-
-// ============================================================================
-//
-// String parsing utility for parsing ldconfig.ldr
-//
-class LDConfigParser
-{
-	public:
-		LDConfigParser (QString inText, char sep);
-
-		bool isAtEnd();
-		bool isAtBeginning();
-		bool getNextToken (QString& val);
-		bool peekNextToken (QString& val);
-		bool getToken (QString& val, const int pos);
-		bool findToken (int& result, char const* needle, int args);
-		int getSize();
-		void rewind();
-		void seek (int amount, bool rel);
-		bool tokenCompare (int inPos, const char* sOther);
-
-		inline QString operator[] (const int idx)
-		{
-			return m_tokens[idx];
-		}
-
-	private:
-		QStringList m_tokens;
-		int m_pos;
-};
-
-void parseLDConfig();
--- a/src/LDObject.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,825 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "Main.h"
-#include "LDObject.h"
-#include "Document.h"
-#include "Misc.h"
-#include "MainWindow.h"
-#include "EditHistory.h"
-#include "GLRenderer.h"
-#include "Colors.h"
-
-cfg (String, ld_defaultname, "");
-cfg (String, ld_defaultuser, "");
-cfg (Int, ld_defaultlicense, 0);
-
-// List of all LDObjects
-static LDObjectList g_LDObjects;
-
-// =============================================================================
-// LDObject constructors
-//
-LDObject::LDObject() :
-	m_isHidden (false),
-	m_isSelected (false),
-	m_parent (null),
-	m_document (null),
-	m_isGLInit (false),
-	qObjListEntry (null)
-{
-	memset (m_coords, 0, sizeof m_coords);
-	chooseID();
-	g_LDObjects << this;
-}
-
-// =============================================================================
-//
-void LDObject::chooseID()
-{
-	int32 id = 1; // 0 shalt be null
-
-	for (LDObject* obj : g_LDObjects)
-	{
-		assert (obj != this);
-
-		if (obj->id() >= id)
-			id = obj->id() + 1;
-	}
-
-	setID (id);
-}
-
-// =============================================================================
-//
-void LDObject::setVertexCoord (int i, Axis ax, double value)
-{
-	Vertex v = vertex (i);
-	v[ax] = value;
-	setVertex (i, v);
-}
-
-LDError::LDError() {}
-
-// =============================================================================
-//
-QString LDComment::asText() const
-{
-	return format ("0 %1", text());
-}
-
-// =============================================================================
-//
-QString LDSubfile::asText() const
-{
-	QString val = format ("1 %1 %2 ", color(), position());
-	val += transform().toString();
-	val += ' ';
-	val += fileInfo()->name();
-	return val;
-}
-
-// =============================================================================
-//
-QString LDLine::asText() const
-{
-	QString val = format ("2 %1", color());
-
-	for (int i = 0; i < 2; ++i)
-		val += format (" %1", vertex (i));
-
-	return val;
-}
-
-// =============================================================================
-//
-QString LDTriangle::asText() const
-{
-	QString val = format ("3 %1", color());
-
-	for (int i = 0; i < 3; ++i)
-		val += format (" %1", vertex (i));
-
-	return val;
-}
-
-// =============================================================================
-//
-QString LDQuad::asText() const
-{
-	QString val = format ("4 %1", color());
-
-	for (int i = 0; i < 4; ++i)
-		val += format (" %1", vertex (i));
-
-	return val;
-}
-
-// =============================================================================
-//
-QString LDCondLine::asText() const
-{
-	QString val = format ("5 %1", color());
-
-	// Add the coordinates
-	for (int i = 0; i < 4; ++i)
-		val += format (" %1", vertex (i));
-
-	return val;
-}
-
-// =============================================================================
-//
-QString LDError::asText() const
-{
-	return contents();
-}
-
-// =============================================================================
-//
-QString LDVertex::asText() const
-{
-	return format ("0 !LDFORGE VERTEX %1 %2", color(), pos);
-}
-
-// =============================================================================
-//
-QString LDEmpty::asText() const
-{
-	return "";
-}
-
-// =============================================================================
-//
-const char* LDBFC::k_statementStrings[] =
-{
-	"CERTIFY CCW",
-	"CCW",
-	"CERTIFY CW",
-	"CW",
-	"NOCERTIFY",
-	"INVERTNEXT",
-	"CLIP",
-	"CLIP CCW",
-	"CLIP CW",
-	"NOCLIP",
-};
-
-QString LDBFC::asText() const
-{
-	return format ("0 BFC %1", LDBFC::k_statementStrings[m_statement]);
-}
-
-// =============================================================================
-//
-QList<LDTriangle*> LDQuad::splitToTriangles()
-{
-	// Create the two triangles based on this quadrilateral:
-	// 0---3       0---3    3
-	// |   |       |  /    /|
-	// |   |  ==>  | /    / |
-	// |   |       |/    /  |
-	// 1---2       1    1---2
-	LDTriangle* tri1 = new LDTriangle (vertex (0), vertex (1), vertex (3));
-	LDTriangle* tri2 = new LDTriangle (vertex (1), vertex (2), vertex (3));
-
-	// The triangles also inherit the quad's color
-	tri1->setColor (color());
-	tri2->setColor (color());
-
-	QList<LDTriangle*> triangles;
-	triangles << tri1;
-	triangles << tri2;
-	return triangles;
-}
-
-// =============================================================================
-//
-void LDObject::replace (LDObject* other)
-{
-	long idx = lineNumber();
-	assert (idx != -1);
-
-	// Replace the instance of the old object with the new object
-	document()->setObject (idx, other);
-
-	// Remove the old object
-	destroy();
-}
-
-// =============================================================================
-//
-void LDObject::swap (LDObject* other)
-{
-	assert (document() == other->document());
-	document()->swapObjects (this, other);
-}
-
-// =============================================================================
-//
-LDLine::LDLine (Vertex v1, Vertex v2)
-{
-	setVertex (0, v1);
-	setVertex (1, v2);
-}
-
-// =============================================================================
-//
-LDQuad::LDQuad (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3)
-{
-	setVertex (0, v0);
-	setVertex (1, v1);
-	setVertex (2, v2);
-	setVertex (3, v3);
-}
-
-// =============================================================================
-//
-LDObject::~LDObject() {}
-
-// =============================================================================
-//
-LDSubfile::~LDSubfile() {}
-
-// =============================================================================
-//
-void LDObject::destroy()
-{
-	// If this object was selected, unselect it now
-	if (isSelected())
-		unselect();
-
-	// If this object was associated to a file, remove it off it now
-	if (document())
-		document()->forgetObject (this);
-
-	// Delete the GL lists
-	GL::deleteLists (this);
-
-	// Remove this object from the list of LDObjects
-	g_LDObjects.removeOne (this);
-
-	delete this;
-}
-
-// =============================================================================
-//
-static void transformObject (LDObject* obj, Matrix transform, Vertex pos, int parentcolor)
-{
-	switch (obj->type())
-	{
-		case LDObject::ELine:
-		case LDObject::ECondLine:
-		case LDObject::ETriangle:
-		case LDObject::EQuad:
-
-			for (int i = 0; i < obj->vertices(); ++i)
-			{
-				Vertex v = obj->vertex (i);
-				v.transform (transform, pos);
-				obj->setVertex (i, v);
-			}
-
-			break;
-
-		case LDObject::ESubfile:
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			Matrix newMatrix = transform * ref->transform();
-			Vertex newpos = ref->position();
-
-			newpos.transform (transform, pos);
-			ref->setPosition (newpos);
-			ref->setTransform (newMatrix);
-		}
-		break;
-
-		default:
-			break;
-	}
-
-	if (obj->color() == maincolor)
-		obj->setColor (parentcolor);
-}
-
-// =============================================================================
-//
-LDObjectList LDSubfile::inlineContents (InlineFlags flags)
-{
-	LDObjectList objs = fileInfo()->inlineContents (flags);
-
-	// Transform the objects
-	for (LDObject* obj : objs)
-	{
-		// Set the parent now so we know what inlined the object.
-		obj->setParent (this);
-		transformObject (obj, transform(), position(), color());
-	}
-
-	return objs;
-}
-
-// =============================================================================
-//
-long LDObject::lineNumber() const
-{
-	assert (document() != null);
-
-	for (int i = 0; i < document()->getObjectCount(); ++i)
-		if (document()->getObject (i) == this)
-			return i;
-
-	return -1;
-}
-
-// =============================================================================
-//
-void LDObject::moveObjects (LDObjectList objs, const bool up)
-{
-	if (objs.isEmpty())
-		return;
-
-	// If we move down, we need to iterate the array in reverse order.
-	const long start = up ? 0 : (objs.size() - 1);
-	const long end = up ? objs.size() : -1;
-	const long incr = up ? 1 : -1;
-	LDObjectList objsToCompile;
-	LDDocument* file = objs[0]->document();
-
-	for (long i = start; i != end; i += incr)
-	{
-		LDObject* obj = objs[i];
-
-		const long idx = obj->lineNumber(),
-				   target = idx + (up ? -1 : 1);
-
-		if ((up && idx == 0) || (!up && idx == (long) (file->objects().size() - 1)))
-		{
-			// One of the objects hit the extrema. If this happens, this should be the first
-			// object to be iterated on. Thus, nothing has changed yet and it's safe to just
-			// abort the entire operation.
-			assert (i == start);
-			return;
-		}
-
-		objsToCompile << obj;
-		objsToCompile << file->getObject (target);
-
-		obj->swap (file->getObject (target));
-	}
-
-	removeDuplicates (objsToCompile);
-
-	// The objects need to be recompiled, otherwise their pick lists are left with
-	// the wrong index colors which messes up selection.
-	for (LDObject* obj : objsToCompile)
-		g_win->R()->compileObject (obj);
-}
-
-// =============================================================================
-//
-QString LDObject::typeName (LDObject::Type type)
-{
-	LDObject* obj = LDObject::getDefault (type);
-	QString name = obj->typeName();
-	obj->destroy();
-	return name;
-}
-
-// =============================================================================
-//
-QString LDObject::describeObjects (const LDObjectList& objs)
-{
-	bool firstDetails = true;
-	QString text = "";
-
-	if (objs.isEmpty())
-		return "nothing"; // :)
-
-	for (long i = 0; i < ENumTypes; ++i)
-	{
-		Type objType = (Type) i;
-		int count = 0;
-
-		for (LDObject * obj : objs)
-			if (obj->type() == objType)
-				count++;
-
-		if (count == 0)
-			continue;
-
-		if (!firstDetails)
-			text += ", ";
-
-		QString noun = format ("%1%2", typeName (objType), plural (count));
-
-		// Plural of "vertex" is "vertices", correct that
-		if (objType == EVertex && count != 1)
-			noun = "vertices";
-
-		text += format ("%1 %2", count, noun);
-		firstDetails = false;
-	}
-
-	return text;
-}
-
-// =============================================================================
-//
-LDObject* LDObject::topLevelParent()
-{
-	if (parent() == null)
-		return this;
-
-	LDObject* it = this;
-
-	while (it->parent() != null)
-		it = it->parent();
-
-	return it;
-}
-
-// =============================================================================
-//
-LDObject* LDObject::next() const
-{
-	long idx = lineNumber();
-	assert (idx != -1);
-
-	if (idx == (long) document()->getObjectCount() - 1)
-		return null;
-
-	return document()->getObject (idx + 1);
-}
-
-// =============================================================================
-//
-LDObject* LDObject::previous() const
-{
-	long idx = lineNumber();
-	assert (idx != -1);
-
-	if (idx == 0)
-		return null;
-
-	return document()->getObject (idx - 1);
-}
-
-// =============================================================================
-//
-void LDObject::move (Vertex vect)
-{
-	if (hasMatrix())
-	{
-		LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (this);
-		mo->setPosition (mo->position() + vect);
-	}
-	elif (type() == LDObject::EVertex)
-	{
-		// ugh
-		static_cast<LDVertex*> (this)->pos += vect;
-	}
-	else
-	{
-		for (int i = 0; i < vertices(); ++i)
-			setVertex (i, vertex (i) + vect);
-	}
-}
-
-// =============================================================================
-//
-#define CHECK_FOR_OBJ(N) \
-	if (type == LDObject::E##N) \
-		return new LD##N;
-
-LDObject* LDObject::getDefault (const LDObject::Type type)
-{
-	CHECK_FOR_OBJ (Comment)
-	CHECK_FOR_OBJ (BFC)
-	CHECK_FOR_OBJ (Line)
-	CHECK_FOR_OBJ (CondLine)
-	CHECK_FOR_OBJ (Subfile)
-	CHECK_FOR_OBJ (Triangle)
-	CHECK_FOR_OBJ (Quad)
-	CHECK_FOR_OBJ (Empty)
-	CHECK_FOR_OBJ (BFC)
-	CHECK_FOR_OBJ (Error)
-	CHECK_FOR_OBJ (Vertex)
-	CHECK_FOR_OBJ (Overlay)
-	return null;
-}
-
-// =============================================================================
-//
-void LDObject::invert() {}
-void LDBFC::invert() {}
-void LDEmpty::invert() {}
-void LDComment::invert() {}
-void LDError::invert() {}
-
-// =============================================================================
-//
-void LDTriangle::invert()
-{
-	// Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1.
-	// Thus, we swap 1 and 2.
-	Vertex tmp = vertex (1);
-	setVertex (1, vertex (2));
-	setVertex (2, tmp);
-
-	return;
-}
-
-// =============================================================================
-//
-void LDQuad::invert()
-{
-	// Quad: 0 -> 1 -> 2 -> 3
-	// rev:  0 -> 3 -> 2 -> 1
-	// Thus, we swap 1 and 3.
-	Vertex tmp = vertex (1);
-	setVertex (1, vertex (3));
-	setVertex (3, tmp);
-}
-
-// =============================================================================
-//
-void LDSubfile::invert()
-{
-	// Subfiles are inverted when they're prefixed with
-	// a BFC INVERTNEXT statement. Thus we need to toggle this status.
-	// For flat primitives it's sufficient that the determinant is
-	// flipped but I don't have a method for checking flatness yet.
-	// Food for thought...
-
-	int idx = lineNumber();
-
-	if (idx > 0)
-	{
-		LDBFC* bfc = dynamic_cast<LDBFC*> (previous());
-
-		if (bfc && bfc->statement() == LDBFC::InvertNext)
-		{
-			// This is prefixed with an invertnext, thus remove it.
-			bfc->destroy();
-			return;
-		}
-	}
-
-	// Not inverted, thus prefix it with a new invertnext.
-	LDBFC* bfc = new LDBFC (LDBFC::InvertNext);
-	document()->insertObj (idx, bfc);
-}
-
-// =============================================================================
-//
-static void invertLine (LDObject* line)
-{
-	// For lines, we swap the vertices. I don't think that a
-	// cond-line's control points need to be swapped, do they?
-	Vertex tmp = line->vertex (0);
-	line->setVertex (0, line->vertex (1));
-	line->setVertex (1, tmp);
-}
-
-void LDLine::invert()
-{
-	invertLine (this);
-}
-
-void LDCondLine::invert()
-{
-	invertLine (this);
-}
-
-void LDVertex::invert() {}
-
-// =============================================================================
-//
-LDLine* LDCondLine::demote()
-{
-	LDLine* repl = new LDLine;
-
-	for (int i = 0; i < repl->vertices(); ++i)
-		repl->setVertex (i, vertex (i));
-
-	repl->setColor (color());
-
-	replace (repl);
-	return repl;
-}
-
-// =============================================================================
-//
-LDObject* LDObject::fromID (int id)
-{
-	for (LDObject* obj : g_LDObjects)
-		if (obj->id() == id)
-			return obj;
-
-	return null;
-}
-
-// =============================================================================
-//
-QString LDOverlay::asText() const
-{
-	return format ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6",
-		fileName(), camera(), x(), y(), width(), height());
-}
-
-void LDOverlay::invert() {}
-
-// =============================================================================
-// Hook the set accessors of certain properties to this changeProperty function.
-// It takes care of history management so we can capture low-level changes, this
-// makes history stuff work out of the box.
-//
-template<class T> static void changeProperty (LDObject* obj, T* ptr, const T& val)
-{
-	long idx;
-
-	if (*ptr == val)
-		return;
-
-	if (obj->document() && (idx = obj->lineNumber()) != -1)
-	{
-		QString before = obj->asText();
-		*ptr = val;
-		QString after = obj->asText();
-
-		if (before != after)
-			obj->document()->addToHistory (new EditHistory (idx, before, after));
-	}
-	else
-		*ptr = val;
-}
-
-// =============================================================================
-//
-void LDObject::setColor (const int& val)
-{
-	changeProperty (this, &m_color, val);
-}
-
-// =============================================================================
-//
-const Vertex& LDObject::vertex (int i) const
-{
-	return m_coords[i]->data();
-}
-
-// =============================================================================
-//
-void LDObject::setVertex (int i, const Vertex& vert)
-{
-	changeProperty (this, &m_coords[i], LDSharedVertex::getSharedVertex (vert));
-}
-
-// =============================================================================
-//
-void LDMatrixObject::setPosition (const Vertex& a)
-{
-	changeProperty (linkPointer(), &m_position, LDSharedVertex::getSharedVertex (a));
-}
-
-// =============================================================================
-//
-void LDMatrixObject::setTransform (const Matrix& val)
-{
-	changeProperty (linkPointer(), &m_transform, val);
-}
-
-// =============================================================================
-//
-static QMap<Vertex, LDSharedVertex*> g_sharedVerts;
-
-LDSharedVertex* LDSharedVertex::getSharedVertex (const Vertex& a)
-{
-	auto it = g_sharedVerts.find (a);
-
-	if (it == g_sharedVerts.end())
-	{
-		LDSharedVertex* v = new LDSharedVertex (a);
-		g_sharedVerts[a] = v;
-		return v;
-	}
-
-	return *it;
-}
-
-// =============================================================================
-//
-void LDSharedVertex::addRef (LDObject* a)
-{
-	m_refs << a;
-}
-
-// =============================================================================
-//
-void LDSharedVertex::delRef (LDObject* a)
-{
-	m_refs.removeOne (a);
-
-	if (m_refs.empty())
-	{
-		g_sharedVerts.remove (m_data);
-		delete this;
-	}
-}
-
-// =============================================================================
-//
-void LDObject::select()
-{
-	assert (document() != null);
-	document()->addToSelection (this);
-}
-
-// =============================================================================
-//
-void LDObject::unselect()
-{
-	assert (document() != null);
-	document()->removeFromSelection (this);
-}
-
-// =============================================================================
-//
-QString getLicenseText (int id)
-{
-	switch (id)
-	{
-		case 0:
-			return g_CALicense;
-
-		case 1:
-			return g_nonCALicense;
-
-		case 2:
-			return "";
-	}
-
-	assert (false);
-	return "";
-}
-
-// =============================================================================
-//
-LDObject* LDObject::createCopy() const
-{
-	/*
-	LDObject* copy = clone();
-	copy->setFile (null);
-	copy->setGLInit (false);
-	copy->chooseID();
-	copy->setSelected (false);
-	*/
-
-	/*
-	LDObject* copy = getDefault (getType());
-	copy->setColor (color());
-
-	if (hasMatrix())
-	{
-		LDMatrixObject* copyMo = static_cast<LDMatrixObject*> (copy);
-		const LDMatrixObject* mo = static_cast<const LDMatrixObject*> (this);
-		copyMo->setPosition (mo->getPosition());
-		copyMo->setTransform (mo->transform());
-	}
-	else
-	{
-		for (int i = 0; i < vertices(); ++i)
-			copy->setVertex (getVertex (i));
-	}
-
-	switch (getType())
-	{
-		case Subfile:
-		{
-			LDSubfile* copyRef = static_cast<LDSubfile*> (copy);
-			const LDSubfile* ref = static_cast<const LDSubfile*> (this);
-
-			copyRef->setFileInfo (ref->fileInfo());
-		}
-	}
-	*/
-
-	LDObject* copy = parseLine (asText());
-	return copy;
-}
\ No newline at end of file
--- a/src/LDObject.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,553 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include "Main.h"
-#include "Types.h"
-#include "misc/DocumentPointer.h"
-
-#define LDOBJ(T)										\
-protected:												\
-	virtual LD##T* clone() override						\
-	{													\
-		return new LD##T (*this);						\
-	}													\
-														\
-public:													\
-	virtual LDObject::Type type() const override		\
-	{													\
-		return LDObject::E##T;							\
-	}													\
-	virtual QString asText() const override;			\
-	virtual void invert() override;
-
-#define LDOBJ_NAME(N)          virtual QString typeName() const override { return #N; }
-#define LDOBJ_VERTICES(V)      virtual int vertices() const override { return V; }
-#define LDOBJ_SETCOLORED(V)    virtual bool isColored() const override { return V; }
-#define LDOBJ_COLORED          LDOBJ_SETCOLORED (true)
-#define LDOBJ_UNCOLORED        LDOBJ_SETCOLORED (false)
-
-#define LDOBJ_CUSTOM_SCEMANTIC virtual bool isScemantic() const override
-#define LDOBJ_SCEMANTIC        LDOBJ_CUSTOM_SCEMANTIC { return true; }
-#define LDOBJ_NON_SCEMANTIC    LDOBJ_CUSTOM_SCEMANTIC { return false; }
-
-#define LDOBJ_SETMATRIX(V)     virtual bool hasMatrix() const override { return V; }
-#define LDOBJ_HAS_MATRIX       LDOBJ_SETMATRIX (true)
-#define LDOBJ_NO_MATRIX        LDOBJ_SETMATRIX (false)
-
-class QListWidgetItem;
-class LDSubfile;
-class LDDocument;
-class LDSharedVertex;
-
-// =============================================================================
-// LDObject
-//
-// Base class object for all object types. Each LDObject represents a single line
-// in the LDraw code file. The virtual method getType returns an enumerator
-// which is a token of the object's type. The object can be casted into
-// sub-classes based on this enumerator.
-// =============================================================================
-class LDObject
-{
-	PROPERTY (public,		bool,			isHidden,		setHidden,		STOCK_WRITE)
-	PROPERTY (public,		bool,			isSelected,		setSelected,	STOCK_WRITE)
-	PROPERTY (public,		LDObject*,		parent,			setParent,		STOCK_WRITE)
-	PROPERTY (public,		LDDocument*,	document,		setDocument,	STOCK_WRITE)
-	PROPERTY (private,		int,			id,				setID,			STOCK_WRITE)
-	PROPERTY (public,		int,			color,			setColor,		CUSTOM_WRITE)
-	PROPERTY (public,		bool,			isGLInit,		setGLInit,		STOCK_WRITE)
-
-	public:
-		// Object type codes.
-		enum Type
-		{
-			ESubfile,        // Object represents a sub-file reference
-			EQuad,           // Object represents a quadrilateral
-			ETriangle,       // Object represents a triangle
-			ELine,           // Object represents a line
-			ECondLine,       // Object represents a conditional line
-			EVertex,         // Object is a vertex, LDForge extension object
-			EBFC,            // Object represents a BFC statement
-			EOverlay,        // Object contains meta-info about an overlay image.
-			EComment,        // Object represents a comment
-			EError,          // Object is the result of failed parsing
-			EEmpty,          // Object represents an empty line
-			EUnidentified,   // Unknown object type (some functions return this; TODO: they probably should not)
-			ENumTypes        // Amount of object types
-		};
-
-		LDObject();
-
-		// Makes a copy of this object
-		LDObject*					createCopy() const;
-
-		// Deletes this object
-		void						destroy();
-
-		// Index (i.e. line number) of this object
-		long						lineNumber() const;
-
-		// Type enumerator of this object
-		virtual Type				type() const = 0;
-
-		// Get a vertex by index
-		const Vertex&				vertex (int i) const;
-
-		// Type name of this object
-		virtual QString				typeName() const = 0;
-
-		// Does this object have a matrix and position? (see LDMatrixObject)
-		virtual bool				hasMatrix() const = 0;
-
-		// Inverts this object (winding is reversed)
-		virtual void				invert() = 0;
-
-		// Is this object colored?
-		virtual bool				isColored() const = 0;
-
-		// Does this object have meaning in the part model?
-		virtual bool				isScemantic() const = 0;
-
-		// Moves this object using the given vertex as a movement List
-		void						move (Vertex vect);
-
-		// Object after this in the current file
-		LDObject*					next() const;
-
-		// Object prior to this in the current file
-		LDObject*					previous() const;
-
-		// This object as LDraw code
-		virtual QString				asText() const = 0;
-
-		// Replace this LDObject with another LDObject. Object is deleted in the process.
-		void						replace (LDObject* other);
-
-		// Selects this object.
-		void						select();
-
-		// Set a vertex to the given value
-		void						setVertex (int i, const Vertex& vert);
-
-		// Set a single coordinate of a vertex
-		void						setVertexCoord (int i, Axis ax, double value);
-
-		// Swap this object with another.
-		void						swap (LDObject* other);
-
-		// What object in the current file ultimately references this?
-		LDObject*					topLevelParent();
-
-		// Removes this object from selection // TODO: rename to deselect?
-		void						unselect();
-
-		// Number of vertices this object has // TODO: rename to getNumVertices
-		virtual int					vertices() const = 0;
-
-		// Get type name by enumerator
-		static QString typeName (LDObject::Type type);
-
-		// Returns a default-constructed LDObject by the given type
-		static LDObject* getDefault (const LDObject::Type type);
-
-		// TODO: move this to LDDocument?
-		static void moveObjects (LDObjectList objs, const bool up);
-
-		// Get a description of a list of LDObjects
-		static QString describeObjects (const LDObjectList& objs);
-		static LDObject* fromID (int id);
-
-		// TODO: make these private!
-		// OpenGL list for this object
-		uint glLists[4];
-
-		// Object list entry for this object
-		QListWidgetItem* qObjListEntry;
-
-	protected:
-		// LDObjects are to be deleted with the deleteSelf() method, not with
-		// operator delete. This is because it seems virtual functions cannot
-		// be properly called from the destructor, thus a normal method must
-		// be used instead. The destructor also doesn't seem to be able to
-		// be private without causing a truckload of problems so it's protected
-		// instead.
-		virtual ~LDObject();
-		void chooseID();
-
-	private:
-		virtual LDObject* clone() = 0;
-		LDSharedVertex*	m_coords[4];
-};
-
-// =============================================================================
-// LDSharedVertex
-//
-// For use as coordinates of LDObjects. Keeps count of references.
-// =============================================================================
-class LDSharedVertex
-{
-	public:
-		inline const Vertex& data() const
-		{
-			return m_data;
-		}
-
-		inline operator const Vertex&() const
-		{
-			return m_data;
-		}
-
-		void addRef (LDObject* a);
-		void delRef (LDObject* a);
-
-		static LDSharedVertex* getSharedVertex (const Vertex& a);
-
-	protected:
-		LDSharedVertex (const Vertex& a) : m_data (a) {}
-
-	private:
-		LDObjectList m_refs;
-		Vertex m_data;
-};
-
-// =============================================================================
-//
-// Common code for objects with matrices. This class is multiple-derived in
-// and thus not used directly other than as a common storage point for matrices
-// and vertices.
-//
-// The link pointer is a pointer to this object's LDObject self - since this is
-// multiple-derived in, static_cast or dynamic_cast won't budge here.
-//
-// In 0.1-alpha, there was a separate 'radial' type which had a position and
-// matrix as well. Even though right now only LDSubfile uses this, I'm keeping
-// this class distinct in case I get new extension ideas. :)
-//
-class LDMatrixObject
-{
-	PROPERTY (public,	LDObject*,	linkPointer,	setLinkPointer,	STOCK_WRITE)
-	PROPERTY (public,	Matrix,		transform,		setTransform,	CUSTOM_WRITE)
-
-	public:
-		LDMatrixObject() :
-			m_position (LDSharedVertex::getSharedVertex (g_origin)) {}
-
-		LDMatrixObject (const Matrix& transform, const Vertex& pos) :
-			m_transform (transform),
-			m_position (LDSharedVertex::getSharedVertex (pos)) {}
-
-		inline const Vertex& position() const
-		{
-			return m_position->data();
-		}
-
-		void setCoordinate (const Axis ax, double value)
-		{
-			Vertex v = position();
-			v[ax] = value;
-			setPosition (v);
-		}
-
-		void setPosition (const Vertex& a);
-
-	private:
-		LDSharedVertex*	m_position;
-};
-
-// =============================================================================
-//
-// Represents a line in the LDraw file that could not be properly parsed. It is
-// represented by a (!) ERROR in the code view. It exists for the purpose of
-// allowing garbage lines be debugged and corrected within LDForge.
-//
-class LDError : public LDObject
-{
-	LDOBJ (Error)
-	LDOBJ_NAME (error)
-	LDOBJ_VERTICES (0)
-	LDOBJ_UNCOLORED
-	LDOBJ_SCEMANTIC
-	LDOBJ_NO_MATRIX
-	PROPERTY (public,	QString,	fileReferenced, setFileReferenced,	STOCK_WRITE)
-	PROPERTY (private,	QString,	contents,		setContents,		STOCK_WRITE)
-	PROPERTY (private,	QString,	reason,			setReason,			STOCK_WRITE)
-
-	public:
-		LDError();
-		LDError (QString contents, QString reason) :
-			m_contents (contents),
-			m_reason (reason) {}
-};
-
-// =============================================================================
-//
-// Represents an empty line in the LDraw code file.
-//
-class LDEmpty : public LDObject
-{
-	LDOBJ (Empty)
-	LDOBJ_NAME (empty)
-	LDOBJ_VERTICES (0)
-	LDOBJ_UNCOLORED
-	LDOBJ_NON_SCEMANTIC
-	LDOBJ_NO_MATRIX
-};
-
-// =============================================================================
-//
-// Represents a code-0 comment in the LDraw code file.
-//
-class LDComment : public LDObject
-{
-	PROPERTY (public, QString, text, setText, STOCK_WRITE)
-	LDOBJ (Comment)
-	LDOBJ_NAME (comment)
-	LDOBJ_VERTICES (0)
-	LDOBJ_UNCOLORED
-	LDOBJ_NON_SCEMANTIC
-	LDOBJ_NO_MATRIX
-
-	public:
-		LDComment() {}
-		LDComment (QString text) : m_text (text) {}
-};
-
-// =============================================================================
-//
-// Represents a 0 BFC statement in the LDraw code. eStatement contains the type
-// of this statement.
-//
-class LDBFC : public LDObject
-{
-	public:
-		enum Statement
-		{
-			CertifyCCW,
-			CCW,
-			CertifyCW,
-			CW,
-			NoCertify,
-			InvertNext,
-			Clip,
-			ClipCCW,
-			ClipCW,
-			NoClip,
-			NumStatements
-		};
-
-		LDOBJ (BFC)
-		LDOBJ_NAME (bfc)
-		LDOBJ_VERTICES (0)
-		LDOBJ_UNCOLORED
-		LDOBJ_CUSTOM_SCEMANTIC { return (statement() == InvertNext); }
-		LDOBJ_NO_MATRIX
-		PROPERTY (public, Statement, statement, setStatement, STOCK_WRITE)
-
-	public:
-		LDBFC() {}
-		LDBFC (const LDBFC::Statement type) :
-			m_statement (type) {}
-
-		// Statement strings
-		static const char* k_statementStrings[];
-};
-
-// =============================================================================
-// LDSubfile
-//
-// Represents a single code-1 subfile reference.
-// =============================================================================
-class LDSubfile : public LDObject, public LDMatrixObject
-{
-	LDOBJ (Subfile)
-	LDOBJ_NAME (subfile)
-	LDOBJ_VERTICES (0)
-	LDOBJ_COLORED
-	LDOBJ_SCEMANTIC
-	LDOBJ_HAS_MATRIX
-	PROPERTY (public, LDDocumentPointer, fileInfo, setFileInfo, STOCK_WRITE)
-
-	public:
-		enum InlineFlag
-		{
-			DeepInline     = (1 << 0),
-			CacheInline    = (1 << 1),
-			RendererInline = (1 << 2),
-			DeepCacheInline = (DeepInline | CacheInline),
-		};
-
-		Q_DECLARE_FLAGS (InlineFlags, InlineFlag)
-
-		LDSubfile()
-		{
-			setLinkPointer (this);
-		}
-
-		// Inlines this subfile. Note that return type is an array of heap-allocated
-		// LDObject copies, they must be deleted manually.
-		LDObjectList inlineContents (InlineFlags flags);
-
-	protected:
-		~LDSubfile();
-};
-
-Q_DECLARE_OPERATORS_FOR_FLAGS (LDSubfile::InlineFlags)
-
-// =============================================================================
-// LDLine
-//
-// Represents a single code-2 line in the LDraw code file. v0 and v1 are the end
-// points of the line. The line is colored with dColor unless uncolored mode is
-// set.
-// =============================================================================
-class LDLine : public LDObject
-{
-	LDOBJ (Line)
-	LDOBJ_NAME (line)
-	LDOBJ_VERTICES (2)
-	LDOBJ_COLORED
-	LDOBJ_SCEMANTIC
-	LDOBJ_NO_MATRIX
-
-	public:
-		LDLine() {}
-		LDLine (Vertex v1, Vertex v2);
-};
-
-// =============================================================================
-// LDCondLine
-//
-// Represents a single code-5 conditional line. The end-points v0 and v1 are
-// inherited from LDLine, c0 and c1 are the control points of this line.
-// =============================================================================
-class LDCondLine : public LDLine
-{
-	LDOBJ (CondLine)
-	LDOBJ_NAME (condline)
-	LDOBJ_VERTICES (4)
-	LDOBJ_COLORED
-	LDOBJ_SCEMANTIC
-	LDOBJ_NO_MATRIX
-
-	public:
-		LDCondLine() {}
-		LDLine* demote();
-};
-
-// =============================================================================
-// LDTriangle
-//
-// Represents a single code-3 triangle in the LDraw code file. Vertices v0, v1
-// and v2 contain the end-points of this triangle. dColor is the color the
-// triangle is colored with.
-// =============================================================================
-class LDTriangle : public LDObject
-{
-	LDOBJ (Triangle)
-	LDOBJ_NAME (triangle)
-	LDOBJ_VERTICES (3)
-	LDOBJ_COLORED
-	LDOBJ_SCEMANTIC
-	LDOBJ_NO_MATRIX
-
-	public:
-		LDTriangle() {}
-		LDTriangle (Vertex v0, Vertex v1, Vertex v2)
-		{
-			setVertex (0, v0);
-			setVertex (1, v1);
-			setVertex (2, v2);
-		}
-};
-
-// =============================================================================
-// LDQuad
-//
-// Represents a single code-4 quadrilateral. v0, v1, v2 and v3 are the end points
-// of the quad, dColor is the color used for the quad.
-// =============================================================================
-class LDQuad : public LDObject
-{
-	LDOBJ (Quad)
-	LDOBJ_NAME (quad)
-	LDOBJ_VERTICES (4)
-	LDOBJ_COLORED
-	LDOBJ_SCEMANTIC
-	LDOBJ_NO_MATRIX
-
-	public:
-		LDQuad() {}
-		LDQuad (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3);
-
-		// Split this quad into two triangles (note: heap-allocated)
-		QList<LDTriangle*> splitToTriangles();
-};
-
-// =============================================================================
-// LDVertex
-//
-// The vertex is an LDForce-specific extension which represents a single
-// vertex which can be used as a parameter to tools or to store coordinates
-// with. Vertices are a part authoring tool and they should not appear in
-// finished parts.
-// =============================================================================
-class LDVertex : public LDObject
-{
-	LDOBJ (Vertex)
-	LDOBJ_NAME (vertex)
-	LDOBJ_VERTICES (0) // TODO: move pos to vaCoords[0]
-	LDOBJ_COLORED
-	LDOBJ_NON_SCEMANTIC
-	LDOBJ_NO_MATRIX
-
-	public:
-		LDVertex() {}
-
-		Vertex pos;
-};
-
-// =============================================================================
-// LDOverlay
-//
-// Overlay image meta, stored in the header of parts so as to preserve overlay
-// information.
-// =============================================================================
-class LDOverlay : public LDObject
-{
-	LDOBJ (Overlay)
-	LDOBJ_NAME (overlay)
-	LDOBJ_VERTICES (0)
-	LDOBJ_UNCOLORED
-	LDOBJ_NON_SCEMANTIC
-	LDOBJ_NO_MATRIX
-	PROPERTY (public,	int,	 camera,	setCamera,		STOCK_WRITE)
-	PROPERTY (public,	int,	 x,			setX,			STOCK_WRITE)
-	PROPERTY (public,	int,	 y,			setY,			STOCK_WRITE)
-	PROPERTY (public,	int,	 width,		setWidth,		STOCK_WRITE)
-	PROPERTY (public,	int,	 height,	setHeight,		STOCK_WRITE)
-	PROPERTY (public,	QString, fileName,	setFileName,	STOCK_WRITE)
-};
-
-// Other common LDraw stuff
-static const QString g_CALicense ("!LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt");
-static const QString g_nonCALicense ("!LICENSE Not redistributable : see NonCAreadme.txt");
-static const int g_lores = 16;
-static const int g_hires = 48;
-
-QString getLicenseText (int id);
--- a/src/Macros.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#ifndef __GNUC__
-# define __attribute__(X)
-#endif
-
-// =============================================================================
-//
-#define PROPERTY(ACCESS, TYPE, READ, WRITE, WRITETYPE)			\
-private:														\
-	TYPE m_##READ;												\
-																\
-public:															\
-	inline TYPE const& READ() const								\
-	{															\
-		return m_##READ; 										\
-	}															\
-																\
-ACCESS:															\
-	void WRITE (TYPE const& a) PROPERTY_##WRITETYPE (READ)		\
-
-#define PROPERTY_STOCK_WRITE(READ)								\
-	{															\
-		m_##READ = a;											\
-	}
-
-#define PROPERTY_CUSTOM_WRITE(READ)								\
-	;
-
-// =============================================================================
-//
-#define elif(A) else if (A)
-
-// =============================================================================
-//
-#ifdef WIN32
-# define DIRSLASH "\\"
-# define DIRSLASH_CHAR '\\'
-#else // WIN32
-# define DIRSLASH "/"
-# define DIRSLASH_CHAR '/'
-#endif // WIN32
-
-// =============================================================================
-//
-#ifdef __GNUC__
-#define FUNCNAME __PRETTY_FUNCTION__
-#else
-#define FUNCNAME __func__
-#endif // __GNUC__
-
-// =============================================================================
-//
-#define dvalof(A) dprint ("value of '%1' = %2\n", #A, A)
-
-// =============================================================================
-//
-// Replace assert with a version that shows a GUI dialog if possible.
-// On Windows I just can't get the actual error messages otherwise.
-//
-#undef assert
-
-#ifdef DEBUG
-# define assert(N) { ((N) ? (void) 0 : assertionFailure (__FILE__, __LINE__, FUNCNAME, #N)); }
-#else
-# define assert(N) {}
-#endif // DEBUG
-
-#define for_axes(AX) for (const Axis AX : std::initializer_list<const Axis> ({X, Y, Z}))
-
-// =============================================================================
-#ifdef IN_IDE_PARSER // KDevelop workarounds:
-# error IN_IDE_PARSER is defined (this code is only for KDevelop workarounds)
-# define COMPILE_DATE "14-01-10 10:31:09"
-
-# ifndef va_start
-#  define va_start(va, arg)
-# endif // va_start
-
-# ifndef va_end
-#  define va_end(va)
-# endif // va_end
-
-static const char* __func__ = ""; // Current function name
-typedef void FILE; // :|
-#endif // IN_IDE_PARSER
\ No newline at end of file
--- a/src/Main.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QApplication>
-#include <QMessageBox>
-#include <QAbstractButton>
-#include <QFile>
-#include <QTextStream>
-#include <QDir>
-#include "MainWindow.h"
-#include "Document.h"
-#include "Misc.h"
-#include "Configuration.h"
-#include "Colors.h"
-#include "Types.h"
-#include "Primitives.h"
-#include "GLRenderer.h"
-#include "ConfigurationDialog.h"
-#include "Dialogs.h"
-#include "CrashCatcher.h"
-
-QList<LDDocument*> g_loadedFiles;
-MainWindow* g_win = null;
-static QString g_versionString, g_fullVersionString;
-
-const Vertex g_origin (0.0f, 0.0f, 0.0f);
-const Matrix g_identity ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
-
-cfg (Bool, firststart, true);
-
-// =============================================================================
-//
-int main (int argc, char* argv[])
-{
-	QApplication app (argc, argv);
-	app.setOrganizationName (APPNAME);
-	app.setApplicationName (APPNAME);
-	initCrashCatcher();
-	LDDocument::setCurrent (null);
-
-	// Load or create the configuration
-	if (!Config::load())
-	{
-		print ("Creating configuration file...\n");
-
-		if (Config::save())
-			print ("Configuration file successfully created.\n");
-		else
-			critical ("Failed to create configuration file!\n");
-	}
-
-	LDPaths::initPaths();
-	initColors();
-	MainWindow* win = new MainWindow;
-	newFile();
-	win->show();
-
-	// If this is the first start, get the user to configuration. Especially point
-	// them to the profile tab, it's the most important form to fill in.
-	if (firststart)
-	{
-		(new ConfigDialog (ConfigDialog::ProfileTab))->exec();
-		firststart = false;
-		Config::save();
-	}
-
-	loadPrimitives();
-	return app.exec();
-}
--- a/src/Main.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-// =============================================================================
-// This file is included one way or another in every source file of LDForge.
-// Stuff defined and included here is universally included.
-
-#pragma once
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdarg.h>
-#include <QString>
-#include <QTextFormat>
-#include "Macros.h"
-#include "Version.h"
-#include "Configuration.h"
-#include "Format.h"
-
-// Null pointer
-static const std::nullptr_t null = nullptr;
-
-void assertionFailure (const char* file, int line, const char* funcname, const char* expr);
-
-// Version string identifier. These are defined in Version.cc.
-const char* versionString();
-const char* fullVersionString();
--- a/src/MainWindow.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1034 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QGridLayout>
-#include <QMessageBox>
-#include <QEvent>
-#include <QContextMenuEvent>
-#include <QMenuBar>
-#include <QStatusBar>
-#include <QSplitter>
-#include <QListWidget>
-#include <QToolButton>
-#include <QComboBox>
-#include <QDialogButtonBox>
-#include <QToolBar>
-#include <QProgressBar>
-#include <QLabel>
-#include <QFileDialog>
-#include <QPushButton>
-#include <QCoreApplication>
-#include <QTimer>
-#include <QMetaMethod>
-#include "Main.h"
-#include "GLRenderer.h"
-#include "MainWindow.h"
-#include "Document.h"
-#include "Configuration.h"
-#include "Misc.h"
-#include "Colors.h"
-#include "EditHistory.h"
-#include "Widgets.h"
-#include "AddObjectDialog.h"
-#include "MessageLog.h"
-#include "Configuration.h"
-#include "ui_ldforge.h"
-
-static bool g_isSelectionLocked = false;
-
-cfg (Bool, lv_colorize, true);
-cfg (String, gui_colortoolbar, "16:24:|:4:25:14:27:2:3:11:1:22:|:0:72:71:15");
-cfg (Bool, gui_implicitfiles, false);
-extern_cfg (List,		io_recentfiles);
-extern_cfg (Bool,		gl_axes);
-extern_cfg (String,	gl_maincolor);
-extern_cfg (Float,	gl_maincolor_alpha);
-extern_cfg (Bool,		gl_wireframe);
-extern_cfg (Bool,		gl_colorbfc);
-extern_cfg (Bool,		gl_drawangles);
-
-// =============================================================================
-//
-MainWindow::MainWindow (QWidget* parent, Qt::WindowFlags flags) :
-	QMainWindow (parent, flags)
-{
-	g_win = this;
-	ui = new Ui_LDForgeUI;
-	ui->setupUi (this);
-	m_updatingTabs = false;
-	m_renderer = new GLRenderer (this);
-	m_tabs = new QTabBar;
-	ui->verticalLayout->insertWidget (0, m_tabs);
-
-	// Stuff the renderer into its frame
-	QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame);
-	rendererLayout->addWidget (R());
-
-	connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged()));
-	connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*)));
-	connect (m_tabs, SIGNAL (currentChanged(int)), this, SLOT (changeCurrentFile()));
-
-	// Init message log manager
-	m_msglog = new MessageManager;
-	m_msglog->setRenderer (R());
-	m_renderer->setMessageLog (m_msglog);
-	m_quickColors = quickColorsFromConfig();
-	slot_selectionChanged();
-	setStatusBar (new QStatusBar);
-
-	// Make certain actions checkable
-	ui->actionAxes->setChecked (gl_axes);
-	ui->actionWireframe->setChecked (gl_wireframe);
-	ui->actionBFCView->setChecked (gl_colorbfc);
-	updateGridToolBar();
-	updateEditModeActions();
-	updateRecentFilesMenu();
-	updateColorToolbar();
-	updateTitle();
-	updateActionShortcuts();
-
-	setMinimumSize (300, 200);
-
-	connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup()));
-
-	// Connect all actions
-	for (QAction* act : findChildren<QAction*>())
-		if (!act->objectName().isEmpty())
-			connect (act, SIGNAL (triggered()), this, SLOT (slot_action()));
-}
-
-// =============================================================================
-//
-KeySequenceConfig* MainWindow::shortcutForAction (QAction* action)
-{
-	QString keycfgname = format ("key_%1", action->objectName());
-	return KeySequenceConfig::getByName (keycfgname);
-}
-
-// =============================================================================
-//
-void MainWindow::updateActionShortcuts()
-{
-	for (QAction* act : findChildren<QAction*>())
-	{
-		KeySequenceConfig* cfg = shortcutForAction (act);
-
-		if (cfg)
-			act->setShortcut (cfg->getValue());
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::slot_action()
-{
-	// Get the name of the sender object and use it to compose the slot name,
-	// then invoke this slot to call the action.
-	QMetaObject::invokeMethod (this,
-		qPrintable (format ("slot_%1", sender()->objectName())), Qt::DirectConnection);
-	endAction();
-}
-
-// =============================================================================
-//
-void MainWindow::endAction()
-{
-	// Add a step in the history now.
-	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.
-	updateDocumentListItem (getCurrentDocument());
-}
-
-// =============================================================================
-//
-void MainWindow::slot_lastSecondCleanup()
-{
-	delete m_renderer;
-	delete ui;
-}
-
-// =============================================================================
-//
-void MainWindow::updateRecentFilesMenu()
-{
-	// First, clear any items in the recent files menu
-for (QAction * recent : m_recentFiles)
-		delete recent;
-
-	m_recentFiles.clear();
-
-	QAction* first = null;
-
-	for (const QVariant& it : io_recentfiles)
-	{
-		QString file = it.toString();
-		QAction* recent = new QAction (getIcon ("open-recent"), file, this);
-
-		connect (recent, SIGNAL (triggered()), this, SLOT (slot_recentFile()));
-		ui->menuOpenRecent->insertAction (first, recent);
-		m_recentFiles << recent;
-		first = recent;
-	}
-}
-
-// =============================================================================
-//
-QList<LDQuickColor> quickColorsFromConfig()
-{
-	QList<LDQuickColor> colors;
-
-	for (QString colorname : gui_colortoolbar.split (":"))
-	{
-		if (colorname == "|")
-			colors << LDQuickColor::getSeparator();
-		else
-		{
-			LDColor* col = getColor (colorname.toLong());
-
-			if (col != null)
-				colors << LDQuickColor (col, null);
-		}
-	}
-
-	return colors;
-}
-
-// =============================================================================
-//
-void MainWindow::updateColorToolbar()
-{
-	m_colorButtons.clear();
-	ui->colorToolbar->clear();
-
-	for (LDQuickColor& entry : m_quickColors)
-	{
-		if (entry.isSeparator())
-			ui->colorToolbar->addSeparator();
-		else
-		{
-			QToolButton* colorButton = new QToolButton;
-			colorButton->setIcon (makeColorIcon (entry.color(), 22));
-			colorButton->setIconSize (QSize (22, 22));
-			colorButton->setToolTip (entry.color()->name);
-
-			connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor()));
-			ui->colorToolbar->addWidget (colorButton);
-			m_colorButtons << colorButton;
-
-			entry.setToolButton (colorButton);
-		}
-	}
-
-	updateGridToolBar();
-}
-
-// =============================================================================
-//
-void MainWindow::updateGridToolBar()
-{
-	// Ensure that the current grid - and only the current grid - is selected.
-	ui->actionGridCoarse->setChecked (grid == Grid::Coarse);
-	ui->actionGridMedium->setChecked (grid == Grid::Medium);
-	ui->actionGridFine->setChecked (grid == Grid::Fine);
-}
-
-// =============================================================================
-//
-void MainWindow::updateTitle()
-{
-	QString title = format (APPNAME " %1", fullVersionString());
-
-	// Append our current file if we have one
-	if (getCurrentDocument())
-	{
-		if (getCurrentDocument()->name().length() > 0)
-			title += format (": %1", basename (getCurrentDocument()->name()));
-		else
-			title += format (": <anonymous>");
-
-		if (getCurrentDocument()->getObjectCount() > 0 &&
-				getCurrentDocument()->getObject (0)->type() == LDObject::EComment)
-		{
-			// Append title
-			LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0));
-			title += format (": %1", comm->text());
-		}
-
-		if (getCurrentDocument()->hasUnsavedChanges())
-			title += '*';
-	}
-
-#ifdef DEBUG
-	title += " [debug build]";
-#elif BUILD_ID != BUILD_RELEASE
-	title += " [pre-release build]";
-#endif // DEBUG
-
-#ifdef COMPILE_DATE
-	title += " (built " COMPILE_DATE ")";
-#endif // COMPILE_DATE
-
-	setWindowTitle (title);
-}
-
-// =============================================================================
-//
-int MainWindow::deleteSelection()
-{
-	if (selection().isEmpty())
-		return 0;
-
-	LDObjectList selCopy = selection();
-
-	// Delete the objects that were being selected
-	for (LDObject* obj : selCopy)
-		obj->destroy();
-
-	refresh();
-	return selCopy.size();
-}
-
-// =============================================================================
-//
-void MainWindow::buildObjList()
-{
-	if (!getCurrentDocument())
-		return;
-
-	// Lock the selection while we do this so that refreshing the object list
-	// doesn't trigger selection updating so that the selection doesn't get lost
-	// while this is done.
-	g_isSelectionLocked = true;
-
-	for (int i = 0; i < ui->objectList->count(); ++i)
-		delete ui->objectList->item (i);
-
-	ui->objectList->clear();
-
-	for (LDObject* obj : getCurrentDocument()->objects())
-	{
-		QString descr;
-
-		switch (obj->type())
-		{
-			case LDObject::EComment:
-			{
-				descr = static_cast<LDComment*> (obj)->text();
-
-				// Remove leading whitespace
-				while (descr[0] == ' ')
-					descr.remove (0, 1);
-
-				break;
-			}
-
-			case LDObject::EEmpty:
-				break; // leave it empty
-
-			case LDObject::ELine:
-			case LDObject::ETriangle:
-			case LDObject::EQuad:
-			case LDObject::ECondLine:
-			{
-				for (int i = 0; i < obj->vertices(); ++i)
-				{
-					if (i != 0)
-						descr += ", ";
-
-					descr += obj->vertex (i).toString (true);
-				}
-				break;
-			}
-
-			case LDObject::EError:
-			{
-				descr = format ("ERROR: %1", obj->asText());
-				break;
-			}
-
-			case LDObject::EVertex:
-			{
-				descr = static_cast<LDVertex*> (obj)->pos.toString (true);
-				break;
-			}
-
-			case LDObject::ESubfile:
-			{
-				LDSubfile* ref = static_cast<LDSubfile*> (obj);
-
-				descr = format ("%1 %2, (", ref->fileInfo()->getDisplayName(), ref->position().toString (true));
-
-				for (int i = 0; i < 9; ++i)
-					descr += format ("%1%2", ref->transform()[i], (i != 8) ? " " : "");
-
-				descr += ')';
-				break;
-			}
-
-			case LDObject::EBFC:
-			{
-				descr = LDBFC::k_statementStrings[static_cast<LDBFC*> (obj)->statement()];
-				break;
-			}
-
-			case LDObject::EOverlay:
-			{
-				LDOverlay* ovl = static_cast<LDOverlay*> (obj);
-				descr = format ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->camera()],
-					basename (ovl->fileName()), ovl->x(), ovl->y(),
-					ovl->width(), ovl->height());
-				break;
-			}
-
-			default:
-			{
-				descr = obj->typeName();
-				break;
-			}
-		}
-
-		QListWidgetItem* item = new QListWidgetItem (descr);
-		item->setIcon (getIcon (obj->typeName()));
-
-		// Use italic font if hidden
-		if (obj->isHidden())
-		{
-			QFont font = item->font();
-			font.setItalic (true);
-			item->setFont (font);
-		}
-
-		// Color gibberish orange on red so it stands out.
-		if (obj->type() == LDObject::EError)
-		{
-			item->setBackground (QColor ("#AA0000"));
-			item->setForeground (QColor ("#FFAA00"));
-		}
-		elif (lv_colorize && obj->isColored() && obj->color() != maincolor && obj->color() != edgecolor)
-		{
-			// If the object isn't in the main or edge color, draw this
-			// list entry in said color.
-			LDColor* col = getColor (obj->color());
-
-			if (col)
-				item->setForeground (col->faceColor);
-		}
-
-		obj->qObjListEntry = item;
-		ui->objectList->insertItem (ui->objectList->count(), item);
-	}
-
-	g_isSelectionLocked = false;
-	updateSelection();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-void MainWindow::scrollToSelection()
-{
-	if (selection().isEmpty())
-		return;
-
-	LDObject* obj = selection().last();
-	ui->objectList->scrollToItem (obj->qObjListEntry);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_selectionChanged()
-{
-	if (g_isSelectionLocked == true || getCurrentDocument() == null)
-		return;
-
-	// Update the shared selection array, though don't do this if this was
-	// called during GL picking, in which case the GL renderer takes care
-	// of the selection.
-	if (m_renderer->isPicking())
-		return;
-
-	LDObjectList priorSelection = selection();
-
-	// Get the objects from the object list selection
-	getCurrentDocument()->clearSelection();
-	const QList<QListWidgetItem*> items = ui->objectList->selectedItems();
-
-	for (LDObject* obj : getCurrentDocument()->objects())
-	{
-		for (QListWidgetItem* item : items)
-		{
-			if (item == obj->qObjListEntry)
-			{
-				obj->select();
-				break;
-			}
-		}
-	}
-
-	// Update the GL renderer
-	LDObjectList compound = priorSelection + selection();
-	removeDuplicates (compound);
-
-	for (LDObject* obj : compound)
-		m_renderer->compileObject (obj);
-
-	m_renderer->update();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_recentFile()
-{
-	QAction* qAct = static_cast<QAction*> (sender());
-	openMainFile (qAct->text());
-}
-
-// =============================================================================
-//
-void MainWindow::slot_quickColor()
-{
-	QToolButton* button = static_cast<QToolButton*> (sender());
-	LDColor* col = null;
-
-	for (const LDQuickColor& entry : m_quickColors)
-	{
-		if (entry.toolButton() == button)
-		{
-			col = entry.color();
-			break;
-		}
-	}
-
-	if (col == null)
-		return;
-
-	int newColor = col->index;
-
-	for (LDObject* obj : selection())
-	{
-		if (obj->isColored() == false)
-			continue; // uncolored object
-
-		obj->setColor (newColor);
-		R()->compileObject (obj);
-	}
-
-	endAction();
-	refresh();
-}
-
-// =============================================================================
-//
-int MainWindow::getInsertionPoint()
-{
-	// If we have a selection, put the item after it.
-	if (!selection().isEmpty())
-		return selection().last()->lineNumber() + 1;
-
-	// Otherwise place the object at the end.
-	return getCurrentDocument()->getObjectCount();
-}
-
-// =============================================================================
-//
-void MainWindow::doFullRefresh()
-{
-	buildObjList();
-	m_renderer->hardRefresh();
-}
-
-// =============================================================================
-//
-void MainWindow::refresh()
-{
-	buildObjList();
-	m_renderer->update();
-}
-
-// =============================================================================
-//
-void MainWindow::updateSelection()
-{
-	g_isSelectionLocked = true;
-
-	for (LDObject* obj : getCurrentDocument()->objects())
-		obj->setSelected (false);
-
-	ui->objectList->clearSelection();
-
-	for (LDObject* obj : selection())
-	{
-		if (obj->qObjListEntry == null)
-			continue;
-
-		obj->qObjListEntry->setSelected (true);
-		obj->setSelected (true);
-	}
-
-	g_isSelectionLocked = false;
-	slot_selectionChanged();
-}
-
-// =============================================================================
-//
-int MainWindow::getSelectedColor()
-{
-	int result = -1;
-
-	for (LDObject* obj : selection())
-	{
-		if (obj->isColored() == false)
-			continue; // doesn't use color
-
-		if (result != -1 && obj->color() != result)
-			return -1; // No consensus in object color
-
-		if (result == -1)
-			result = obj->color();
-	}
-
-	return result;
-}
-
-// =============================================================================
-//
-LDObject::Type MainWindow::getUniformSelectedType()
-{
-	LDObject::Type result = LDObject::EUnidentified;
-
-	for (LDObject* obj : selection())
-	{
-		if (result != LDObject::EUnidentified && obj->color() != result)
-			return LDObject::EUnidentified;
-
-		if (result == LDObject::EUnidentified)
-			result = obj->type();
-	}
-
-	return result;
-}
-
-// =============================================================================
-//
-void MainWindow::closeEvent (QCloseEvent* ev)
-{
-	// Check whether it's safe to close all files.
-	if (!safeToCloseAll())
-	{
-		ev->ignore();
-		return;
-	}
-
-	// Save the configuration before leaving so that, for instance, grid choice
-	// is preserved across instances.
-	Config::save();
-
-	ev->accept();
-}
-
-// =============================================================================
-//
-void MainWindow::spawnContextMenu (const QPoint pos)
-{
-	const bool single = (selection().size() == 1);
-	LDObject* singleObj = (single) ? selection()[0] : null;
-
-	QMenu* contextMenu = new QMenu;
-
-	if (single && singleObj->type() != LDObject::EEmpty)
-	{
-		contextMenu->addAction (ui->actionEdit);
-		contextMenu->addSeparator();
-	}
-
-	contextMenu->addAction (ui->actionCut);
-	contextMenu->addAction (ui->actionCopy);
-	contextMenu->addAction (ui->actionPaste);
-	contextMenu->addAction (ui->actionDelete);
-	contextMenu->addSeparator();
-	contextMenu->addAction (ui->actionSetColor);
-
-	if (single)
-		contextMenu->addAction (ui->actionEditRaw);
-
-	contextMenu->addAction (ui->actionBorders);
-	contextMenu->addAction (ui->actionSetOverlay);
-	contextMenu->addAction (ui->actionClearOverlay);
-	contextMenu->addAction (ui->actionModeSelect);
-	contextMenu->addAction (ui->actionModeDraw);
-	contextMenu->addAction (ui->actionModeCircle);
-
-	if (selection().size() > 0)
-	{
-		contextMenu->addSeparator();
-		contextMenu->addAction (ui->actionSubfileSelection);
-	}
-
-	if (R()->camera() != GL::EFreeCamera)
-	{
-		contextMenu->addSeparator();
-		contextMenu->addAction (ui->actionSetDrawDepth);
-	}
-
-	contextMenu->exec (pos);
-}
-
-// =============================================================================
-//
-void MainWindow::deleteByColor (int colnum)
-{
-	LDObjectList objs;
-
-	for (LDObject* obj : getCurrentDocument()->objects())
-	{
-		if (!obj->isColored() || obj->color() != colnum)
-			continue;
-
-		objs << obj;
-	}
-
-	for (LDObject* obj : objs)
-		obj->destroy();
-}
-
-// =============================================================================
-//
-void MainWindow::updateEditModeActions()
-{
-	const EditMode mode = R()->editMode();
-	ui->actionModeSelect->setChecked (mode == ESelectMode);
-	ui->actionModeDraw->setChecked (mode == EDrawMode);
-	ui->actionModeCircle->setChecked (mode == ECircleMode);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_editObject (QListWidgetItem* listitem)
-{
-	LDObject* obj = null;
-
-	for (LDObject* it : getCurrentDocument()->objects())
-	{
-		if (it->qObjListEntry == listitem)
-		{
-			obj = it;
-			break;
-		}
-	}
-
-	AddObjectDialog::staticDialog (obj->type(), obj);
-}
-
-// =============================================================================
-//
-bool MainWindow::save (LDDocument* doc, bool saveAs)
-{
-	QString path = doc->fullPath();
-
-	if (saveAs || path.isEmpty())
-	{
-		QString name = doc->defaultName();
-
-		if (!doc->fullPath().isEmpty()) 
-			name = doc->fullPath();
-		elif (!doc->name().isEmpty())
-			name = doc->name();
-
-		name.replace ("\\", "/");
-		path = QFileDialog::getSaveFileName (g_win, tr ("Save As"),
-			name, tr ("LDraw files (*.dat *.ldr)"));
-
-		if (path.isEmpty())
-		{
-			// User didn't give a file name, abort.
-			return false;
-		}
-	}
-
-	if (doc->save (path))
-	{
-		if (doc == getCurrentDocument())
-			updateTitle();
-
-		print ("Saved to %1.", path);
-
-		// Add it to recent files
-		addRecentFile (path);
-		return true;
-	}
-
-	QString message = format (tr ("Failed to save to %1: %2"), path, strerror (errno));
-
-	// Tell the user the save failed, and give the option for saving as with it.
-	QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win);
-
-	// Add a save-as button
-	QPushButton* saveAsBtn = new QPushButton (tr ("Save As"));
-	saveAsBtn->setIcon (getIcon ("file-save-as"));
-	dlg.addButton (saveAsBtn, QMessageBox::ActionRole);
-	dlg.setDefaultButton (QMessageBox::Close);
-	dlg.exec();
-
-	if (dlg.clickedButton() == saveAsBtn)
-		return save (doc, true); // yay recursion!
-
-	return false;
-}
-
-void MainWindow::addMessage (QString msg)
-{
-	m_msglog->addLine (msg);
-}
-
-// ============================================================================
-void ObjectList::contextMenuEvent (QContextMenuEvent* ev)
-{
-	g_win->spawnContextMenu (ev->globalPos());
-}
-
-// =============================================================================
-//
-QPixmap getIcon (QString iconName)
-{
-	return (QPixmap (format (":/icons/%1.png", iconName)));
-}
-
-// =============================================================================
-//
-bool confirm (const QString& message)
-{
-	return confirm (MainWindow::tr ("Confirm"), message);
-}
-
-// =============================================================================
-//
-bool confirm (const QString& title, const QString& message)
-{
-	return QMessageBox::question (g_win, title, message,
-		(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes;
-}
-
-// =============================================================================
-//
-void critical (const QString& message)
-{
-	QMessageBox::critical (g_win, MainWindow::tr ("Error"), message,
-		(QMessageBox::Close), QMessageBox::Close);
-}
-
-// =============================================================================
-//
-QIcon makeColorIcon (LDColor* colinfo, const int size)
-{
-	// Create an image object and link a painter to it.
-	QImage img (size, size, QImage::Format_ARGB32);
-	QPainter paint (&img);
-	QColor col = colinfo->faceColor;
-
-	if (colinfo->index == maincolor)
-	{
-		// Use the user preferences for main color here
-		col = gl_maincolor;
-		col.setAlphaF (gl_maincolor_alpha);
-	}
-
-	// Paint the icon border
-	paint.fillRect (QRect (0, 0, size, size), colinfo->edgeColor);
-
-	// Paint the checkerboard background, visible with translucent icons
-	paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8));
-
-	// Paint the color above the checkerboard
-	paint.fillRect (QRect (1, 1, size - 2, size - 2), col);
-	return QIcon (QPixmap::fromImage (img));
-}
-
-// =============================================================================
-//
-void makeColorComboBox (QComboBox* box)
-{
-	std::map<int, int> counts;
-
-	for (LDObject* obj : getCurrentDocument()->objects())
-	{
-		if (!obj->isColored())
-			continue;
-
-		if (counts.find (obj->color()) == counts.end())
-			counts[obj->color()] = 1;
-		else
-			counts[obj->color()]++;
-	}
-
-	box->clear();
-	int row = 0;
-
-	for (const auto& pair : counts)
-	{
-		LDColor* col = getColor (pair.first);
-		assert (col != null);
-
-		QIcon ico = makeColorIcon (col, 16);
-		box->addItem (ico, format ("[%1] %2 (%3 object%4)",
-			pair.first, col->name, pair.second, plural (pair.second)));
-		box->setItemData (row, pair.first);
-
-		++row;
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::updateDocumentList()
-{
-	m_updatingTabs = true;
-
-	while (m_tabs->count() > 0)
-		m_tabs->removeTab (0);
-
-	for (LDDocument* f : g_loadedFiles)
-	{
-		// Don't list implicit files unless explicitly desired.
-		if (f->isImplicit() && !gui_implicitfiles)
-			continue;
-
-		// Add an item to the list for this file and store the tab index
-		// in the document so we can find documents by tab index.
-		f->setTabIndex (m_tabs->addTab (""));
-		updateDocumentListItem (f);
-	}
-
-	m_updatingTabs = false;
-}
-
-// =============================================================================
-//
-void MainWindow::updateDocumentListItem (LDDocument* doc)
-{
-	bool oldUpdatingTabs = m_updatingTabs;
-	m_updatingTabs = true;
-
-	if (doc->tabIndex() == -1)
-	{
-		// 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.
-		updateDocumentList();
-		return;
-	}
-
-	// If this is the current file, it also needs to be the selected item on
-	// the list.
-	if (doc == getCurrentDocument())
-		m_tabs->setCurrentIndex (doc->tabIndex());
-
-	m_tabs->setTabText (doc->tabIndex(), doc->getDisplayName());
-
-	// If the document.has unsaved changes, draw a little icon next to it to mark that.
-	m_tabs->setTabIcon (doc->tabIndex(), doc->hasUnsavedChanges() ? getIcon ("file-save") : QIcon());
-	m_updatingTabs = oldUpdatingTabs;
-}
-
-// =============================================================================
-//
-// 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 MainWindow::changeCurrentFile()
-{
-	if (m_updatingTabs)
-		return;
-
-	LDDocument* f = null;
-	int tabIndex = m_tabs->currentIndex();
-
-	// Find the file pointer of the item that was selected.
-	for (LDDocument* it : g_loadedFiles)
-	{
-		if (it->tabIndex() == tabIndex)
-		{
-			f = it;
-			break;
-		}
-	}
-
-	// If we picked the same file we're currently on, we don't need to do
-	// anything.
-	if (f == null || f == getCurrentDocument())
-		return;
-
-	LDDocument::setCurrent (f);
-}
-
-// =============================================================================
-//
-void MainWindow::refreshObjectList()
-{
-#if 0
-	ui->objectList->clear();
-	LDDocument* f = getCurrentDocument();
-
-for (LDObject* obj : *f)
-		ui->objectList->addItem (obj->qObjListEntry);
-
-#endif
-
-	buildObjList();
-}
-
-// =============================================================================
-//
-void MainWindow::updateActions()
-{
-	History* his = getCurrentDocument()->history();
-	int pos = his->position();
-	ui->actionUndo->setEnabled (pos != -1);
-	ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1);
-	ui->actionAxes->setChecked (gl_axes);
-	ui->actionBFCView->setChecked (gl_colorbfc);
-	ui->actionDrawAngles->setChecked (gl_drawangles);
-}
-
-// =============================================================================
-//
-QImage imageFromScreencap (uchar* data, int w, int h)
-{
-	// GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well.
-	return QImage (data, w, h, QImage::Format_ARGB32).rgbSwapped().mirrored();
-}
-
-// =============================================================================
-//
-LDQuickColor::LDQuickColor (LDColor* color, QToolButton* toolButton) :
-	m_color (color),
-	m_toolButton (toolButton) {}
-
-// =============================================================================
-//
-LDQuickColor LDQuickColor::getSeparator()
-{
-	return LDQuickColor (null, null);
-}
-
-// =============================================================================
-//
-bool LDQuickColor::isSeparator() const
-{
-	return color() == null;
-}
--- a/src/MainWindow.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,378 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QMainWindow>
-#include <QAction>
-#include <QListWidget>
-#include <QRadioButton>
-#include "Configuration.h"
-#include "LDObject.h"
-#include "ui_ldforge.h"
-
-class MessageManager;
-class MainWindow;
-class LDColor;
-class QToolButton;
-class QDialogButtonBox;
-class GLRenderer;
-class QComboBox;
-class QProgressBar;
-class Ui_LDForgeUI;
-
-// Stuff for dialogs
-#define IMPLEMENT_DIALOG_BUTTONS \
-	bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); \
-	connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept())); \
-	connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); \
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-#define DEFINE_ACTION(NAME, DEFSHORTCUT) \
-	cfg (KeySequence, key_action##NAME, DEFSHORTCUT); \
-	void MainWindow::slot_action##NAME()
-
-// Convenience macros for key sequences.
-#define KEY(N) (Qt::Key_##N)
-#define CTRL(N) (Qt::CTRL | Qt::Key_##N)
-#define SHIFT(N) (Qt::SHIFT | Qt::Key_##N)
-#define CTRL_SHIFT(N) (Qt::CTRL | Qt::SHIFT | Qt::Key_##N)
-
-// =============================================================================
-class LDQuickColor
-{
-	PROPERTY (public,	LDColor*,		color,		setColor,		STOCK_WRITE)
-	PROPERTY (public,	QToolButton*,	toolButton,	setToolButton,	STOCK_WRITE)
-
-	public:
-		LDQuickColor (LDColor* color, QToolButton* toolButton);
-		bool isSeparator() const;
-
-		static LDQuickColor getSeparator();
-};
-
-//!
-//! Object list class for MainWindow
-//!
-class ObjectList : public QListWidget
-{
-	Q_OBJECT
-
-	protected:
-		void contextMenuEvent (QContextMenuEvent* ev);
-};
-
-//!
-//! \brief The main window class.
-//!
-//! The MainWindow is LDForge's main GUI. It hosts the renderer, the object list,
-//! the message log, etc. Contains \c slot_action(), which is what all actions
-//! connect to.
-//!
-class MainWindow : public QMainWindow
-{
-	Q_OBJECT
-
-	public:
-		//! Constructs the main window
-		explicit MainWindow (QWidget* parent = null, Qt::WindowFlags flags = 0);
-
-		//! Rebuilds the object list, located to the right of the GUI.
-		void buildObjList();
-
-		//! Updates the window title.
-		void updateTitle();
-
-		//! Builds the object list and tells the GL renderer to init a full
-		//! refresh.
-		void doFullRefresh();
-
-		//! Builds the object list and tells the GL renderer to do a soft update.
-		void refresh();
-
-		//! \returns the suggested position to place a new object at.
-		int getInsertionPoint();
-
-		//! Updates the quick color toolbar
-		void updateColorToolbar();
-
-		//! Rebuilds the recent files submenu
-		void updateRecentFilesMenu();
-
-		//! Sets the selection based on what's selected in the object list.
-		void updateSelection();
-
-		//! Updates the grids, selects the selected grid and deselects others.
-		void updateGridToolBar();
-
-		//! Updates the edit modes, current one is selected and others are deselected.
-		void updateEditModeActions();
-
-		//! Rebuilds the document tab list.
-		void updateDocumentList();
-
-		//! Updates the document tab for \c doc. If no such tab exists, the
-		//! document list is rebuilt instead.
-		void updateDocumentListItem (LDDocument* doc);
-
-		//! \returns the uniform selected color (i.e. 4 if everything selected is
-		//! red), -1 if there is no such consensus.
-		int getSelectedColor();
-
-		//! \returns the uniform selected type (i.e. \c LDObject::ELine if everything
-		//! selected is a line), \c LDObject::EUnidentified if there is no such
-		//! consensus.
-		LDObject::Type getUniformSelectedType();
-
-		//! Automatically scrolls the object list so that it points to the first
-		//! selected object.
-		void scrollToSelection();
-
-		//! Spawns the context menu at the given position.
-		void spawnContextMenu (const QPoint pos);
-
-		//! Deletes all selected objects.
-		//! \returns the count of deleted objects.
-		int deleteSelection();
-
-		//! Deletes all objects by the given color number.
-		void deleteByColor (int colnum);
-
-		//! Tries to save the given document.
-		//! \param doc the document to save
-		//! \param saveAs if true, always ask for a file path
-		//! \returns whether the save was successful
-		bool save (LDDocument* doc, bool saveAs);
-
-		//! Updates various actions, undo/redo are set enabled/disabled where
-		//! appropriate, togglable actions are updated based on configuration,
-		//! etc.
-		void updateActions();
-
-		//! \returns a pointer to the renderer
-		inline GLRenderer* R()
-		{
-			return m_renderer;
-		}
-
-		//! Sets the quick color list to the given list of colors.
-		inline void setQuickColors (const QList<LDQuickColor>& colors)
-		{
-			m_quickColors = colors;
-			updateColorToolbar();
-		}
-
-		//! Adds a message to the renderer's message manager.
-		void addMessage (QString msg);
-
-		//! Updates the object list. Right now this just rebuilds it.
-		void refreshObjectList();
-
-		//! Updates all actions to ensure they have the correct shortcut as
-		//! defined in the configuration entries.
-		void updateActionShortcuts();
-
-		//! Gets the shortcut configuration for the given \c action
-		KeySequenceConfig* shortcutForAction (QAction* action);
-
-		void endAction();
-
-	public slots:
-		void changeCurrentFile();
-		void slot_action();
-		void slot_actionNew();
-		void slot_actionNewFile();
-		void slot_actionOpen();
-		void slot_actionDownloadFrom();
-		void slot_actionSave();
-		void slot_actionSaveAs();
-		void slot_actionSaveAll();
-		void slot_actionClose();
-		void slot_actionCloseAll();
-		void slot_actionInsertFrom();
-		void slot_actionExportTo();
-		void slot_actionSettings();
-		void slot_actionSetLDrawPath();
-		void slot_actionScanPrimitives();
-		void slot_actionExit();
-		void slot_actionResetView();
-		void slot_actionAxes();
-		void slot_actionWireframe();
-		void slot_actionBFCView();
-		void slot_actionSetOverlay();
-		void slot_actionClearOverlay();
-		void slot_actionScreenshot();
-		void slot_actionInsertRaw();
-		void slot_actionNewSubfile();
-		void slot_actionNewLine();
-		void slot_actionNewTriangle();
-		void slot_actionNewQuad();
-		void slot_actionNewCLine();
-		void slot_actionNewComment();
-		void slot_actionNewBFC();
-		void slot_actionNewVertex();
-		void slot_actionUndo();
-		void slot_actionRedo();
-		void slot_actionCut();
-		void slot_actionCopy();
-		void slot_actionPaste();
-		void slot_actionDelete();
-		void slot_actionSelectAll();
-		void slot_actionSelectByColor();
-		void slot_actionSelectByType();
-		void slot_actionModeDraw();
-		void slot_actionModeSelect();
-		void slot_actionModeCircle();
-		void slot_actionSetDrawDepth();
-		void slot_actionSetColor();
-		void slot_actionAutocolor();
-		void slot_actionUncolorize();
-		void slot_actionInline();
-		void slot_actionInlineDeep();
-		void slot_actionInvert();
-		void slot_actionMakePrimitive();
-		void slot_actionSplitQuads();
-		void slot_actionEditRaw();
-		void slot_actionBorders();
-		void slot_actionCornerVerts();
-		void slot_actionRoundCoordinates();
-		void slot_actionVisibilityHide();
-		void slot_actionVisibilityReveal();
-		void slot_actionVisibilityToggle();
-		void slot_actionReplaceCoords();
-		void slot_actionFlip();
-		void slot_actionDemote();
-		void slot_actionYtruder();
-		void slot_actionRectifier();
-		void slot_actionIntersector();
-		void slot_actionIsecalc();
-		void slot_actionCoverer();
-		void slot_actionEdger2();
-		void slot_actionHelp();
-		void slot_actionAbout();
-		void slot_actionAboutQt();
-		void slot_actionGridCoarse();
-		void slot_actionGridMedium();
-		void slot_actionGridFine();
-		void slot_actionEdit();
-		void slot_actionMoveUp();
-		void slot_actionMoveDown();
-		void slot_actionMoveXNeg();
-		void slot_actionMoveXPos();
-		void slot_actionMoveYNeg();
-		void slot_actionMoveYPos();
-		void slot_actionMoveZNeg();
-		void slot_actionMoveZPos();
-		void slot_actionRotateXNeg();
-		void slot_actionRotateXPos();
-		void slot_actionRotateYNeg();
-		void slot_actionRotateYPos();
-		void slot_actionRotateZNeg();
-		void slot_actionRotateZPos();
-		void slot_actionRotationPoint();
-		void slot_actionAddHistoryLine();
-		void slot_actionJumpTo();
-		void slot_actionSubfileSelection();
-		void slot_actionDrawAngles();
-
-	protected:
-		void closeEvent (QCloseEvent* ev);
-
-	private:
-		GLRenderer*			m_renderer;
-		LDObjectList		m_sel;
-		QList<LDQuickColor>	m_quickColors;
-		QList<QToolButton*>	m_colorButtons;
-		QList<QAction*>		m_recentFiles;
-		MessageManager*		m_msglog;
-		Ui_LDForgeUI*		ui;
-		QTabBar*			m_tabs;
-		bool				m_updatingTabs;
-
-	private slots:
-		void slot_selectionChanged();
-		void slot_recentFile();
-		void slot_quickColor();
-		void slot_lastSecondCleanup();
-		void slot_editObject (QListWidgetItem* listitem);
-};
-
-//! Pointer to the instance of MainWindow.
-extern MainWindow* g_win;
-
-//! Get an icon by name from the resources directory.
-QPixmap getIcon (QString iconName);
-
-//! \returns a list of quick colors based on the configuration entry.
-QList<LDQuickColor> quickColorsFromConfig();
-
-//! Asks the user a yes/no question with the given \c message and the given
-//! window \c title.
-//! \returns true if the user answered yes, false if no.
-bool confirm (const QString& title, const QString& message); // Generic confirm prompt
-
-//! An overload of \c confirm(), this asks the user a yes/no question with the
-//! given \c message.
-//! \returns true if the user answered yes, false if no.
-bool confirm (const QString& message);
-
-//! Displays an error prompt with the given \c message
-void critical (const QString& message);
-
-//! Makes an icon of \c size x \c size pixels to represent \c colinfo
-QIcon makeColorIcon (LDColor* colinfo, const int size);
-
-//! Fills the given combo-box with color information
-void makeColorComboBox (QComboBox* box);
-
-//! \returns a QImage from the given raw GL \c data
-QImage imageFromScreencap (uchar* data, int w, int h);
-
-//!
-//! Takes in pairs of radio buttons and respective values and finds the first
-//! selected one.
-//! \returns returns the value of the first found radio button that was checked
-//! \returns by the user.
-//!
-template<class T>
-T radioSwitch (const T& defval, QList<Pair<QRadioButton*, T>> haystack)
-{
-	for (Pair<QRadioButton*, const T&> i : haystack)
-		if (i.first->isChecked())
-			return i.second;
-
-	return defval;
-}
-
-//!
-//! Takes in pairs of radio buttons and respective values and checks the first
-//! found radio button whose respsective value matches \c expr have the given value.
-//!
-template<class T>
-void radioDefault (const T& expr, QList<Pair<QRadioButton*, T>> haystack)
-{
-	for (Pair<QRadioButton*, const T&> i : haystack)
-	{
-		if (i.second == expr)
-		{
-			i.first->setChecked (true);
-			return;
-		}
-	}
-}
--- a/src/MessageLog.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QTimer>
-#include <QDate>
-#include "MessageLog.h"
-#include "GLRenderer.h"
-#include "MainWindow.h"
-
-static const int g_maxMessages = 5;
-static const int g_expiry = 5;
-static const int g_fadeTime = 500; // msecs
-
-// =============================================================================
-//
-MessageManager::MessageManager (QObject* parent) :
-			QObject (parent)
-{
-	m_ticker = new QTimer;
-	m_ticker->start (100);
-	connect (m_ticker, SIGNAL (timeout()), this, SLOT (tick()));
-}
-
-// =============================================================================
-//
-MessageManager::Line::Line (QString text) :
-			text (text),
-			alpha (1.0f),
-			expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {}
-
-// =============================================================================
-//
-bool MessageManager::Line::update (bool& changed)
-{
-	changed = false;
-	QDateTime now = QDateTime::currentDateTime();
-	int msec = now.msecsTo (expiry);
-
-	if (now >= expiry)
-	{
-		// Message line has expired
-		changed = true;
-		return false;
-	}
-
-	if (msec <= g_fadeTime)
-	{
-		// Message line has not expired but is fading out
-		alpha = ( (float) msec) / g_fadeTime;
-		changed = true;
-	}
-
-	return true;
-}
-
-// =============================================================================
-// Add a line to the message manager.
-//
-void MessageManager::addLine (QString line)
-{
-	// If there's too many entries, pop the excess out
-	while (m_lines.size() >= g_maxMessages)
-		m_lines.removeFirst();
-
-	m_lines << Line (line);
-
-	// Update the renderer view
-	if (renderer())
-		renderer()->update();
-}
-
-// =============================================================================
-// Ticks the message manager. All lines are ticked and the renderer scene is
-// redrawn if something changed.
-//
-void MessageManager::tick()
-{
-	if (m_lines.isEmpty())
-		return;
-
-	bool changed = false;
-
-	for (int i = 0; i < m_lines.size(); ++i)
-	{
-		bool lineChanged;
-
-		if (!m_lines[i].update (lineChanged))
-			m_lines.removeAt (i--);
-
-		changed |= lineChanged;
-	}
-
-	if (changed && renderer())
-		renderer()->update();
-}
-
-// =============================================================================
-//
-const QList<MessageManager::Line>& MessageManager::getLines() const
-{
-	return m_lines;
-}
-
-// =============================================================================
-//
-void printToLog (const QString& msg)
-{
-	for (QString& a : msg.split ("\n", QString::SkipEmptyParts))
-	{
-		if (g_win != null)
-			g_win->addMessage (a);
-
-		// Also print it to stdout
-		fprint (stdout, "%1\n", a);
-	}
-}
--- a/src/MessageLog.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QObject>
-#include <QDate>
-#include "Main.h"
-#include "Types.h"
-
-class GLRenderer;
-class QTimer;
-
-//!
-//! \brief Manages the list of messages at the top-left of the renderer.
-//!
-//! The message manager is an object which keeps track of messages that appear
-//! on the renderer's screen. Each line is contained in a separate object which
-//! contains the text, expiry time and alpha. The message manager is doubly
-//! linked to its corresponding renderer.
-//!
-//! Message manager calls its \c tick() function regularly to update the messages,
-//! where each line's expiry is checked for. Lines begin to fade out when nearing
-//! their expiry. If the message manager's lines change, the renderer undergoes
-//! repainting.
-//!
-class MessageManager : public QObject
-{
-	Q_OBJECT
-	PROPERTY (public, GLRenderer*, renderer, setRenderer, STOCK_WRITE)
-
-	public:
-		//! \class MessageManager::Line
-		//! A single line of the message log.
-		class Line
-		{
-			public:
-				//! Constructs a line with the given \c text
-				Line (QString text);
-
-				//! Check this line's expiry and update alpha accordingly.
-				//! \c changed is updated to whether the line has somehow
-				//! changed since the last update.
-				//! \returns true if the line is to still stick around, false
-				//! \returns if it expired.
-				bool update (bool& changed);
-
-				QString text;
-				float alpha;
-				QDateTime expiry;
-		};
-
-		//! Constructs the message manager.
-		explicit MessageManager (QObject* parent = null);
-
-		//! Adds a line with the given \c text to the message manager.
-		void addLine (QString line);
-
-		//! \returns all active lines in the message manager.
-		const QList<Line>& getLines() const;
-
-	private:
-		QList<Line>	m_lines;
-		QTimer*		m_ticker;
-
-	private slots:
-		//! Ticks the manager. This is called by the timer to update
-		//! the messages.
-		void tick();
-};
--- a/src/Misc.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,303 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <math.h>
-#include <locale.h>
-#include <QColor>
-#include "Main.h"
-#include "Misc.h"
-#include "MainWindow.h"
-#include "Dialogs.h"
-#include "Document.h"
-#include "ui_rotpoint.h"
-#include "misc/DocumentPointer.cc"
-#include "misc/RingFinder.cc"
-#include "misc/InvokationDeferer.cc"
-
-// Prime number table.
-const int g_primes[NUM_PRIMES] =
-{
-	2,    3,    5,    7,    11,   13,   17,   19,   23,   29,
-	31,   37,   41,   43,   47,   53,   59,   61,   67,   71,
-	73,   79,   83,   89,   97,   101,  103,  107,  109,  113,
-	127,  131,  137,  139,  149,  151,  157,  163,  167,  173,
-	179,  181,  191,  193,  197,  199,  211,  223,  227,  229,
-	233,  239,  241,  251,  257,  263,  269,  271,  277,  281,
-	283,  293,  307,  311,  313,  317,  331,  337,  347,  349,
-	353,  359,  367,  373,  379,  383,  389,  397,  401,  409,
-	419,  421,  431,  433,  439,  443,  449,  457,  461,  463,
-	467,  479,  487,  491,  499,  503,  509,  521,  523,  541,
-	547,  557,  563,  569,  571,  577,  587,  593,  599,  601,
-	607,  613,  617,  619,  631,  641,  643,  647,  653,  659,
-	661,  673,  677,  683,  691,  701,  709,  719,  727,  733,
-	739,  743,  751,  757,  761,  769,  773,  787,  797,  809,
-	811,  821,  823,  827,  829,  839,  853,  857,  859,  863,
-	877,  881,  883,  887,  907,  911,  919,  929,  937,  941,
-	947,  953,  967,  971,  977,  983,  991,  997, 1009, 1013,
-	1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
-	1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151,
-	1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
-	1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
-	1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
-	1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451,
-	1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
-	1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583,
-	1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657,
-	1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733,
-	1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811,
-	1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889,
-	1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987,
-	1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053,
-	2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129,
-	2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213,
-	2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287,
-	2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357,
-	2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423,
-	2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531,
-	2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617,
-	2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687,
-	2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741,
-	2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819,
-	2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903,
-	2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999,
-	3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079,
-	3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181,
-	3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257,
-	3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331,
-	3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413,
-	3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511,
-	3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571,
-};
-
-static const long g_e10[] =
-{
-	1l,
-	10l,
-	100l,
-	1000l,
-	10000l,
-	100000l,
-	1000000l,
-	10000000l,
-	100000000l,
-	1000000000l,
-};
-
-// =============================================================================
-//
-// Grid stuff
-//
-cfg (Int,		grid,					Grid::Medium);
-cfg (Float,		grid_coarse_x,			5.0f);
-cfg (Float,		grid_coarse_y,			5.0f);
-cfg (Float,		grid_coarse_z,			5.0f);
-cfg (Float,		grid_coarse_angle,		45.0f);
-cfg (Float, 	grid_medium_x,			1.0f);
-cfg (Float,		grid_medium_y,			1.0f);
-cfg (Float,		grid_medium_z,			1.0f);
-cfg (Float,		grid_medium_angle,		22.5f);
-cfg (Float,		grid_fine_x,			0.1f);
-cfg (Float,		grid_fine_y,			0.1f);
-cfg (Float,		grid_fine_z,			0.1f);
-cfg (Float,		grid_fine_angle,		7.5f);
-cfg (Int,		edit_rotpoint,			0);
-cfg (Vertex,	edit_customrotpoint,	g_origin);
-
-const gridinfo g_GridInfo[3] =
-{
-	{ "Coarse", { &grid_coarse_x, &grid_coarse_y, &grid_coarse_z, &grid_coarse_angle }},
-	{ "Medium", { &grid_medium_x, &grid_medium_y, &grid_medium_z, &grid_medium_angle }},
-	{ "Fine",   { &grid_fine_x,   &grid_fine_y,   &grid_fine_z,   &grid_fine_angle   }}
-};
-
-// =============================================================================
-//
-// Snap the given coordinate value on the current grid's given axis.
-//
-double Grid::snap (double in, const Grid::Config axis)
-{
-	const double gridval = *currentGrid().confs[axis];
-	const long mult = abs (in / gridval);
-	const bool neg = (in < 0);
-	double out = mult * gridval;
-
-	if (abs<double> (in) - (mult * gridval) > gridval / 2)
-		out += gridval;
-
-	if (neg && out != 0)
-		out *= -1;
-
-	return out;
-}
-
-// =============================================================================
-//
-bool numeric (const QString& tok)
-{
-	bool gotDot = false;
-
-	for (int i = 0; i < tok.length(); ++i)
-	{
-		const QChar c = tok[i];
-
-		// Allow leading hyphen for negatives
-		if (i == 0 && c == '-')
-			continue;
-
-		// Check for decimal point
-		if (!gotDot && c == '.')
-		{
-			gotDot = true;
-			continue;
-		}
-
-		if (c >= '0' && c <= '9')
-			continue; // Digit
-
-		// If the above cases didn't catch this character, it was
-		// illegal and this is therefore not a number.
-		return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-void simplify (int& numer, int& denom)
-{
-	bool repeat;
-
-	do
-	{
-		repeat = false;
-
-		for (int x = 0; x < NUM_PRIMES; x++)
-		{
-			const int prime = g_primes[NUM_PRIMES - x - 1];
-
-			if (numer <= prime || denom <= prime)
-				continue;
-
-			if ((numer % prime == 0) && (denom % prime == 0))
-			{
-				numer /= prime;
-				denom /= prime;
-				repeat = true;
-				break;
-			}
-		}
-	}
-	while (repeat);
-}
-
-// =============================================================================
-//
-Vertex rotPoint (const LDObjectList& objs)
-{
-	switch ((ERotationPoint) edit_rotpoint)
-	{
-		case EObjectOrigin:
-		{
-			LDBoundingBox box;
-
-			// Calculate center vertex
-			for (LDObject* obj : objs)
-			{
-				if (obj->hasMatrix())
-					box << dynamic_cast<LDMatrixObject*> (obj)->position();
-				else
-					box << obj;
-			}
-
-			return box.center();
-		}
-
-		case EWorldOrigin:
-		{
-			return g_origin;
-		}
-
-		case ECustomPoint:
-		{
-			return edit_customrotpoint;
-		}
-	}
-
-	return Vertex();
-}
-
-// =============================================================================
-//
-void configRotationPoint()
-{
-	QDialog* dlg = new QDialog;
-	Ui::RotPointUI ui;
-	ui.setupUi (dlg);
-
-	switch ((ERotationPoint) edit_rotpoint)
-	{
-		case EObjectOrigin:
-			ui.objectPoint->setChecked (true);
-			break;
-
-		case EWorldOrigin:
-			ui.worldPoint->setChecked (true);
-			break;
-
-		case ECustomPoint:
-			ui.customPoint->setChecked (true);
-			break;
-	}
-
-	ui.customX->setValue (edit_customrotpoint.x());
-	ui.customY->setValue (edit_customrotpoint.y());
-	ui.customZ->setValue (edit_customrotpoint.z());
-
-	if (!dlg->exec())
-		return;
-
-	edit_rotpoint =
-		(ui.objectPoint->isChecked()) ? EObjectOrigin :
-		(ui.worldPoint->isChecked())  ? EWorldOrigin :
-		ECustomPoint;
-
-	edit_customrotpoint.x() = ui.customX->value();
-	edit_customrotpoint.y() = ui.customY->value();
-	edit_customrotpoint.z() = ui.customZ->value();
-}
-
-// =============================================================================
-//
-QString join (QList<StringFormatArg> vals, QString delim)
-{
-	QStringList list;
-
-	for (const StringFormatArg& arg : vals)
-		list << arg.text();
-
-	return list.join (delim);
-}
-
-// =============================================================================
-//
-void roundToDecimals (double& a, int decimals)
-{
-	assert (decimals >= 0 && decimals < (signed) (sizeof g_e10 / sizeof *g_e10));
-	a = round (a * g_e10[decimals]) / g_e10[decimals];
-}
\ No newline at end of file
--- a/src/Misc.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QVector>
-#include "Configuration.h"
-#include "Main.h"
-#include "Types.h"
-
-#define NUM_PRIMES 500
-
-class LDDocument;
-class QColor;
-class QAction;
-
-// Prime numbers
-extern const int g_primes[NUM_PRIMES];
-
-// Returns whether a given string represents a floating point number.
-bool numeric (const QString& tok);
-
-// Simplifies the given fraction.
-void simplify (int& numer, int& denom);
-
-void roundToDecimals (double& a, int decimals);
-
-QString join (QList< StringFormatArg > vals, QString delim = " ");
-
-// Grid stuff
-struct gridinfo
-{
-	const char* const	name;
-	float* const		confs[4];
-};
-
-extern_cfg (Int, grid);
-static const int g_NumGrids = 3;
-extern const gridinfo g_GridInfo[3];
-
-inline const gridinfo& currentGrid()
-{
-	return g_GridInfo[grid];
-}
-
-// =============================================================================
-enum ERotationPoint
-{
-	EObjectOrigin,
-	EWorldOrigin,
-	ECustomPoint
-};
-
-Vertex rotPoint (const LDObjectList& objs);
-void configRotationPoint();
-
-// =============================================================================
-namespace Grid
-{
-	enum Type
-	{
-		Coarse,
-		Medium,
-		Fine
-	};
-
-	enum Config
-	{
-		X,
-		Y,
-		Z,
-		Angle
-	};
-
-	double snap (double value, const Grid::Config axis);
-}
-
-// =============================================================================
-// Plural expression
-template<class T> static inline const char* plural (T n)
-{
-	return (n != 1) ? "s" : "";
-}
-
-// =============================================================================
-// Templated clamp
-template<class T> static inline T clamp (T a, T min, T max)
-{
-	return (a > max) ? max : (a < min) ? min : a;
-}
-
-// Templated minimum
-template<class T> static inline T min (T a, T b)
-{
-	return (a < b) ? a : b;
-}
-
-// Templated maximum
-template<class T> static inline T max (T a, T b)
-{
-	return (a > b) ? a : b;
-}
-
-// Templated absolute value
-template<class T> static inline T abs (T a)
-{
-	return (a >= 0) ? a : -a;
-}
-
-template<class T> inline bool isZero (T a)
-{
-	return abs<T> (a) < 0.0001;
-}
-
-template<class T> inline bool isInteger (T a)
-{
-	return isZero (a - (int) a);
-}
-
-template<class T> void removeDuplicates (QList<T>& a)
-{
-	std::sort (a.begin(), a.end());
-	a.erase (std::unique (a.begin(), a.end()), a.end());
-}
--- a/src/PartDownloader.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,539 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QNetworkAccessManager>
-#include <QNetworkRequest>
-#include <QNetworkReply>
-#include <QDir>
-#include <QProgressBar>
-#include <QPushButton>
-#include "PartDownloader.h"
-#include "ui_downloadfrom.h"
-#include "Types.h"
-#include "MainWindow.h"
-#include "Document.h"
-#include "GLRenderer.h"
-#include "ConfigurationDialog.h"
-
-cfg (String,	net_downloadpath,	"");
-cfg (Bool,		net_guesspaths,	true);
-cfg (Bool,		net_autoclose,		true);
-
-const QString g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/");
-
-// =============================================================================
-//
-void PartDownloader::staticBegin()
-{
-	QString path = getDownloadPath();
-
-	if (path == "" || QDir (path).exists() == false)
-	{
-		critical (PartDownloader::tr ("You need to specify a valid path for "
-			"downloaded files in the configuration to download paths."));
-
-		(new ConfigDialog (ConfigDialog::DownloadTab, null))->exec();
-		return;
-	}
-
-	PartDownloader* dlg = new PartDownloader;
-	dlg->exec();
-}
-
-// =============================================================================
-//
-QString PartDownloader::getDownloadPath()
-{
-	QString path = net_downloadpath;
-
-#if DIRSLASH_CHAR != '/'
-	path.replace (DIRSLASH, "/");
-#endif
-
-	return path;
-}
-
-// =============================================================================
-//
-PartDownloader::PartDownloader (QWidget* parent) : QDialog (parent)
-{
-	setInterface (new Ui_DownloadFrom);
-	interface()->setupUi (this);
-	interface()->fname->setFocus();
-	interface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch);
-
-	setDownloadButton (new QPushButton (tr ("Download")));
-	interface()->buttonBox->addButton (downloadButton(), QDialogButtonBox::ActionRole);
-	getButton (Abort)->setEnabled (false);
-
-	connect (interface()->source, SIGNAL (currentIndexChanged (int)),
-		this, SLOT (sourceChanged (int)));
-	connect (interface()->buttonBox, SIGNAL (clicked (QAbstractButton*)),
-		this, SLOT (buttonClicked (QAbstractButton*)));
-}
-
-// =============================================================================
-//
-PartDownloader::~PartDownloader()
-{
-	delete interface();
-}
-
-// =============================================================================
-//
-QString PartDownloader::getURL() const
-{
-	const Source src = getSource();
-	QString dest;
-
-	switch (src)
-	{
-		case PartsTracker:
-			dest = interface()->fname->text();
-			modifyDestination (dest);
-			return g_unofficialLibraryURL + dest;
-
-		case CustomURL:
-			return interface()->fname->text();
-	}
-
-	// Shouldn't happen
-	return "";
-}
-
-// =============================================================================
-//
-void PartDownloader::modifyDestination (QString& dest) const
-{
-	dest = dest.simplified();
-
-	// If the user doesn't want us to guess, stop right here.
-	if (net_guesspaths == false)
-		return;
-
-	// Ensure .dat extension
-	if (dest.right (4) != ".dat")
-	{
-		// Remove the existing extension, if any. It may be we're here over a
-		// typo in the .dat extension.
-		const int dotpos = dest.lastIndexOf (".");
-
-		if (dotpos != -1 && dotpos >= dest.length() - 4)
-			dest.chop (dest.length() - dotpos);
-
-		dest += ".dat";
-	}
-
-	// If the part starts with s\ or s/, then use parts/s/. Same goes with
-	// 48\ and p/48/.
-	if (dest.left (2) == "s\\" || dest.left (2) == "s/")
-	{
-		dest.remove (0, 2);
-		dest.prepend ("parts/s/");
-	} elif (dest.left (3) == "48\\" || dest.left (3) == "48/")
-	{
-		dest.remove (0, 3);
-		dest.prepend ("p/48/");
-	}
-
-	/* Try determine where to put this part. We have four directories:
-	   parts/, parts/s/, p/, and p/48/. If we haven't already specified
-	   either parts/ or p/, we need to add it automatically. Part files
-	   are numbers wit a possible u prefix for parts with unknown number
-	   which can be followed by any of:
-	   - c** (composites)
-	   - d** (formed stickers)
-	   - p** (patterns)
-	   - a lowercase alphabetic letter for variants
-
-	   Subfiles (usually) have an s** prefix, in which case we use parts/s/.
-	   Note that the regex starts with a '^' so it won't catch already fully
-	   given part file names. */
-	QString partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*";
-	QString subpartRegex = partRegex + "s[0-9][0-9]+";
-
-	partRegex += "\\.dat$";
-	subpartRegex += "\\.dat$";
-
-	if (QRegExp (subpartRegex).exactMatch (dest))
-		dest.prepend ("parts/s/");
-	elif (QRegExp (partRegex).exactMatch (dest))
-		dest.prepend ("parts/");
-	elif (dest.left (6) != "parts/" && dest.left (2) != "p/")
-		dest.prepend ("p/");
-}
-
-// =============================================================================
-//
-PartDownloader::Source PartDownloader::getSource() const
-{
-	return (Source) interface()->source->currentIndex();
-}
-
-// =============================================================================
-//
-void PartDownloader::sourceChanged (int i)
-{
-	if (i == CustomURL)
-		interface()->fileNameLabel->setText (tr ("URL:"));
-	else
-		interface()->fileNameLabel->setText (tr ("File name:"));
-}
-
-// =============================================================================
-//
-void PartDownloader::buttonClicked (QAbstractButton* btn)
-{
-	if (btn == getButton (Close))
-	{
-		reject();
-	}
-	elif (btn == getButton (Abort))
-	{
-		setAborted (true);
-
-		for (PartDownloadRequest* req : requests())
-			req->abort();
-	}
-	elif (btn == getButton (Download))
-	{
-		QString dest = interface()->fname->text();
-		setPrimaryFile (null);
-		setAborted (false);
-
-		if (getSource() == CustomURL)
-			dest = basename (getURL());
-
-		modifyDestination (dest);
-
-		if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest))
-		{
-			const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest);
-			if (!confirm (tr ("Overwrite?"), overwritemsg))
-				return;
-		}
-
-		downloadButton()->setEnabled (false);
-		interface()->progress->setEnabled (true);
-		interface()->fname->setEnabled (false);
-		interface()->source->setEnabled (false);
-		downloadFile (dest, getURL(), true);
-		getButton (Close)->setEnabled (false);
-		getButton (Abort)->setEnabled (true);
-		getButton (Download)->setEnabled (false);
-	}
-}
-
-// =============================================================================
-//
-void PartDownloader::downloadFile (QString dest, QString url, bool primary)
-{
-	const int row = interface()->progress->rowCount();
-
-	// Don't download files repeadetly.
-	if (filesToDownload().indexOf (dest) != -1)
-		return;
-
-	modifyDestination (dest);
-	PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this);
-	m_filesToDownload << dest;
-	m_requests << req;
-	interface()->progress->insertRow (row);
-	req->setTableRow (row);
-	req->updateToTable();
-}
-
-// =============================================================================
-//
-void PartDownloader::checkIfFinished()
-{
-	bool failed = isAborted();
-
-	// If there is some download still working, we're not finished.
-	for (PartDownloadRequest* req : requests())
-	{
-		if (!req->isFinished())
-			return;
-
-		if (req->state() == PartDownloadRequest::EFailed)
-			failed = true;
-	}
-
-	for (PartDownloadRequest* req : requests())
-		delete req;
-
-	m_requests.clear();
-
-	// Update everything now
-	if (primaryFile() != null)
-	{
-		LDDocument::setCurrent (primaryFile());
-		reloadAllSubfiles();
-		g_win->doFullRefresh();
-		g_win->R()->resetAngles();
-	}
-
-	if (net_autoclose && !failed)
-	{
-		// Close automatically if desired.
-		accept();
-	}
-	else
-	{
-		// Allow the prompt be closed now.
-		getButton (Abort)->setEnabled (false);
-		getButton (Close)->setEnabled (true);
-	}
-}
-
-// =============================================================================
-//
-QPushButton* PartDownloader::getButton (PartDownloader::Button i)
-{
-	switch (i)
-	{
-		case Download:
-			return downloadButton();
-
-		case Abort:
-			return qobject_cast<QPushButton*> (interface()->buttonBox->button (QDialogButtonBox::Abort));
-
-		case Close:
-			return qobject_cast<QPushButton*> (interface()->buttonBox->button (QDialogButtonBox::Close));
-	}
-
-	return null;
-}
-
-// =============================================================================
-//
-PartDownloadRequest::PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent) :
-	QObject (parent),
-	m_state (ERequesting),
-	m_prompt (parent),
-	m_url (url),
-	m_destinaton (dest),
-	m_filePath (PartDownloader::getDownloadPath() + DIRSLASH + dest),
-	m_networkManager (new QNetworkAccessManager),
-	m_isFirstUpdate (true),
-	m_isPrimary (primary),
-	m_filePointer (null)
-{
-	// Make sure that we have a valid destination.
-	QString dirpath = dirname (filePath());
-
-	QDir dir (dirpath);
-
-	if (dir.exists() == false)
-	{
-		print ("Creating %1...\n", dirpath);
-
-		if (!dir.mkpath (dirpath))
-			critical (format (tr ("Couldn't create the directory %1!"), dirpath));
-	}
-
-	setNetworkReply (networkManager()->get (QNetworkRequest (QUrl (url))));
-	connect (networkReply(), SIGNAL (finished()), this, SLOT (downloadFinished()));
-	connect (networkReply(), SIGNAL (readyRead()), this, SLOT (readyRead()));
-	connect (networkReply(), SIGNAL (downloadProgress (qint64, qint64)),
-		this, SLOT (downloadProgress (qint64, qint64)));
-}
-
-// =============================================================================
-//
-PartDownloadRequest::~PartDownloadRequest() {}
-
-// =============================================================================
-//
-void PartDownloadRequest::updateToTable()
-{
-	const int		labelcol = PartDownloader::PartLabelColumn,
-					progcol = PartDownloader::ProgressColumn;
-	QTableWidget*	table = prompt()->interface()->progress;
-	QProgressBar*	prog;
-
-	switch (state())
-	{
-		case ERequesting:
-		case EDownloading:
-		{
-			prog = qobject_cast<QProgressBar*> (table->cellWidget (tableRow(), progcol));
-
-			if (!prog)
-			{
-				prog = new QProgressBar;
-				table->setCellWidget (tableRow(), progcol, prog);
-			}
-
-			prog->setRange (0, numBytesTotal());
-			prog->setValue (numBytesRead());
-		} break;
-
-		case EFinished:
-		case EFailed:
-		{
-			const QString text = (state() == EFinished)
-				? "<b><span style=\"color: #080\">FINISHED</span></b>"
-				: "<b><span style=\"color: #800\">FAILED</span></b>";
-
-			QLabel* lb = new QLabel (text);
-			lb->setAlignment (Qt::AlignCenter);
-			table->setCellWidget (tableRow(), progcol, lb);
-		} break;
-	}
-
-	QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (tableRow(), labelcol));
-
-	if (isFirstUpdate())
-	{
-		lb = new QLabel (format ("<b>%1</b>", destinaton()), table);
-		table->setCellWidget (tableRow(), labelcol, lb);
-	}
-
-	// Make sure that the cell is big enough to contain the label
-	if (table->columnWidth (labelcol) < lb->width())
-		table->setColumnWidth (labelcol, lb->width());
-
-	setFirstUpdate (true);
-}
-
-// =============================================================================
-//
-void PartDownloadRequest::downloadFinished()
-{
-	if (networkReply()->error() != QNetworkReply::NoError)
-	{
-		if (isPrimary() && !prompt()->isAborted())
-			critical (networkReply()->errorString());
-
-		setState (EFailed);
-	}
-	elif (state() != EFailed)
-		setState (EFinished);
-
-	setNumBytesRead (numBytesTotal());
-	updateToTable();
-
-	if (filePointer())
-	{
-		filePointer()->close();
-		delete filePointer();
-		setFilePointer (null);
-
-		if (state() == EFailed)
-			QFile::remove (filePath());
-	}
-
-	if (state() != EFinished)
-	{
-		prompt()->checkIfFinished();
-		return;
-	}
-
-	// Try to load this file now.
-	LDDocument* f = openDocument (filePath(), false);
-
-	if (!f)
-		return;
-
-	f->setImplicit (!isPrimary());
-
-	// Iterate through this file and check for errors. If there's any that stems
-	// from unknown file references, try resolve that by downloading the reference.
-	// This is why downloading a part may end up downloading multiple files, as
-	// it resolves dependencies.
-	for (LDObject* obj : f->objects())
-	{
-		LDError* err = dynamic_cast<LDError*> (obj);
-
-		if (err == null || err->fileReferenced().isEmpty())
-			continue;
-
-		QString dest = err->fileReferenced();
-		prompt()->modifyDestination (dest);
-		prompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false);
-	}
-
-	if (isPrimary())
-	{
-		addRecentFile (filePath());
-		prompt()->setPrimaryFile (f);
-	}
-
-	prompt()->checkIfFinished();
-}
-
-// =============================================================================
-//
-void PartDownloadRequest::downloadProgress (int64 recv, int64 total)
-{
-	setNumBytesRead (recv);
-	setNumBytesTotal (total);
-	setState (EDownloading);
-	updateToTable();
-}
-
-// =============================================================================
-//
-void PartDownloadRequest::readyRead()
-{
-	if (state() == EFailed)
-		return;
-
-	if (filePointer() == null)
-	{
-		m_filePath.replace ("\\", "/");
-
-		// We have already asked the user whether we can overwrite so we're good
-		// to go here.
-		setFilePointer (new QFile (filePath().toLocal8Bit()));
-
-		if (!filePointer()->open (QIODevice::WriteOnly))
-		{
-			critical (format (tr ("Couldn't open %1 for writing: %2"), filePath(), strerror (errno)));
-			setState (EFailed);
-			networkReply()->abort();
-			updateToTable();
-			prompt()->checkIfFinished();
-			return;
-		}
-	}
-
-	filePointer()->write (networkReply()->readAll());
-}
-
-// =============================================================================
-//
-bool PartDownloadRequest::isFinished() const
-{
-	return state() == EFinished || state() == EFailed;
-}
-
-// =============================================================================
-//
-void PartDownloadRequest::abort()
-{
-	networkReply()->abort();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (DownloadFrom, 0)
-{
-	PartDownloader::staticBegin();
-}
--- a/src/PartDownloader.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QDialog>
-#include "Main.h"
-#include "Types.h"
-
-class LDDocument;
-class QFile;
-class PartDownloadRequest;
-class Ui_DownloadFrom;
-class QNetworkAccessManager;
-class QNetworkRequest;
-class QNetworkReply;
-class QAbstractButton;
-
-// =============================================================================
-//
-class PartDownloader : public QDialog
-{
-	public:
-		enum Source
-		{
-			PartsTracker,
-			CustomURL,
-		};
-
-		enum Button
-		{
-			Download,
-			Abort,
-			Close
-		};
-
-		enum TableColumn
-		{
-			PartLabelColumn,
-			ProgressColumn,
-		};
-
-		using RequestList = QList<PartDownloadRequest*>;
-
-		Q_OBJECT
-		PROPERTY (public,	LDDocument*, 		primaryFile,		setPrimaryFile,		STOCK_WRITE)
-		PROPERTY (public,	bool,				isAborted,			setAborted,			STOCK_WRITE)
-		PROPERTY (private,	Ui_DownloadFrom*,	interface,			setInterface,		STOCK_WRITE)
-		PROPERTY (private,	QStringList,		filesToDownload,	setFilesToDownload,	STOCK_WRITE)
-		PROPERTY (private,	RequestList,		requests,			setRequests,		STOCK_WRITE)
-		PROPERTY (private,	QPushButton*,		downloadButton,		setDownloadButton,	STOCK_WRITE)
-
-	public:
-		explicit		PartDownloader (QWidget* parent = null);
-		virtual			~PartDownloader();
-
-		void			downloadFile (QString dest, QString url, bool primary);
-		QPushButton*	getButton (Button i);
-		QString			getURL() const;
-		Source			getSource() const;
-		void			modifyDestination (QString& dest) const;
-
-		static QString	getDownloadPath();
-		static void		staticBegin();
-
-	public slots:
-		void			buttonClicked (QAbstractButton* btn);
-		void			checkIfFinished();
-		void			sourceChanged (int i);
-};
-
-// =============================================================================
-//
-class PartDownloadRequest : public QObject
-{
-	public:
-		enum EState
-		{
-			ERequesting,
-			EDownloading,
-			EFinished,
-			EFailed,
-		};
-
-		Q_OBJECT
-		PROPERTY (public,	int,					tableRow,		setTableRow,		STOCK_WRITE)
-		PROPERTY (private,	EState,					state,			setState,			STOCK_WRITE)
-		PROPERTY (private,	PartDownloader*,		prompt,			setPrompt,			STOCK_WRITE)
-		PROPERTY (private,	QString,				url,			setURL,				STOCK_WRITE)
-		PROPERTY (private,	QString,				destinaton,		setDestination,		STOCK_WRITE)
-		PROPERTY (private,	QString,				filePath,		setFilePath,		STOCK_WRITE)
-		PROPERTY (private,	QNetworkAccessManager*,	networkManager,	setNetworkManager,	STOCK_WRITE)
-		PROPERTY (private,	QNetworkReply*,			networkReply,	setNetworkReply,	STOCK_WRITE)
-		PROPERTY (private,	bool,					isFirstUpdate,	setFirstUpdate,		STOCK_WRITE)
-		PROPERTY (private,	int64,					numBytesRead,	setNumBytesRead,	STOCK_WRITE)
-		PROPERTY (private,	int64,					numBytesTotal,	setNumBytesTotal,	STOCK_WRITE)
-		PROPERTY (private,	bool,					isPrimary,		setPrimary,			STOCK_WRITE)
-		PROPERTY (private,	QFile*,					filePointer,	setFilePointer,		STOCK_WRITE)
-
-	public:
-		explicit PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent);
-		PartDownloadRequest (const PartDownloadRequest&) = delete;
-		virtual ~PartDownloadRequest();
-		void updateToTable();
-		bool isFinished() const;
-		void operator= (const PartDownloadRequest&) = delete;
-
-	public slots:
-		void downloadFinished();
-		void readyRead();
-		void downloadProgress (qint64 recv, qint64 total);
-		void abort();
-};
--- a/src/Primitives.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,703 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDir>
-#include <QRegExp>
-#include <QFileDialog>
-#include "Document.h"
-#include "MainWindow.h"
-#include "Primitives.h"
-#include "ui_makeprim.h"
-#include "Misc.h"
-#include "Colors.h"
-
-QList<PrimitiveCategory*> g_PrimitiveCategories;
-QList<Primitive> g_primitives;
-static PrimitiveScanner* g_activeScanner = null;
-PrimitiveCategory* g_unmatched = null;
-
-extern_cfg (String, ld_defaultname);
-extern_cfg (String, ld_defaultuser);
-extern_cfg (Int, ld_defaultlicense);
-
-static const QStringList g_radialNameRoots =
-{
-	"edge",
-	"cyli",
-	"disc",
-	"ndis",
-	"ring",
-	"con"
-};
-
-PrimitiveScanner* getActivePrimitiveScanner()
-{
-	return g_activeScanner;
-}
-
-// =============================================================================
-//
-void loadPrimitives()
-{
-	PrimitiveCategory::loadCategories();
-
-	// Try to load prims.cfg
-	QFile conf (Config::filepath ("prims.cfg"));
-
-	if (conf.open (QIODevice::ReadOnly) == false)
-	{
-		// No prims.cfg, build it
-		PrimitiveScanner::start();
-	}
-	else
-	{
-		while (conf.atEnd() == false)
-		{
-			QString line = conf.readLine();
-
-			if (line.endsWith ("\n"))
-				line.chop (1);
-
-			int space = line.indexOf (" ");
-
-			if (space == -1)
-				continue;
-
-			Primitive info;
-			info.name = line.left (space);
-			info.title = line.mid (space + 1);
-			g_primitives << info;
-		}
-
-		PrimitiveCategory::populateCategories();
-		print ("%1 primitives loaded.\n", g_primitives.size());
-	}
-}
-
-// =============================================================================
-//
-static void recursiveGetFilenames (QDir dir, QList<QString>& fnames)
-{
-	QFileInfoList flist = dir.entryInfoList (QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
-
-	for (const QFileInfo& info : flist)
-	{
-		if (info.isDir())
-			recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames);
-		else
-			fnames << info.absoluteFilePath();
-	}
-}
-
-// =============================================================================
-//
-PrimitiveScanner::PrimitiveScanner (QObject* parent) :
-	QObject (parent),
-	m_i (0)
-{
-	g_activeScanner = this;
-	QDir dir (LDPaths::prims());
-	assert (dir.exists());
-	m_baselen = dir.absolutePath().length();
-	recursiveGetFilenames (dir, m_files);
-	emit starting (m_files.size());
-	print ("Scanning primitives...");
-}
-
-// =============================================================================
-//
-PrimitiveScanner::~PrimitiveScanner()
-{
-	g_activeScanner = null;
-}
-
-// =============================================================================
-//
-void PrimitiveScanner::work()
-{
-	int j = min (m_i + 100, m_files.size());
-
-	for (; m_i < j; ++m_i)
-	{
-		QString fname = m_files[m_i];
-		QFile f (fname);
-
-		if (!f.open (QIODevice::ReadOnly))
-			continue;
-
-		Primitive info;
-		info.name = fname.mid (m_baselen + 1);  // make full path relative
-		info.name.replace ('/', '\\');  // use DOS backslashes, they're expected
-		info.category = null;
-		QByteArray titledata = f.readLine();
-
-		if (titledata != QByteArray())
-			info.title = QString::fromUtf8 (titledata);
-
-		info.title = info.title.simplified();
-
-		if (Q_LIKELY (info.title[0] == '0'))
-		{
-			info.title.remove (0, 1);  // remove 0
-			info.title = info.title.simplified();
-		}
-
-		m_prims << info;
-	}
-
-	if (m_i == m_files.size())
-	{
-		// Done with primitives, now save to a config file
-		QString path = Config::filepath ("prims.cfg");
-		QFile conf (path);
-
-		if (!conf.open (QIODevice::WriteOnly | QIODevice::Text))
-			critical (format ("Couldn't write primitive list %1: %2",
-				path, conf.errorString()));
-		else
-		{
-			for (Primitive& info : m_prims)
-				fprint (conf, "%1 %2\r\n", info.name, info.title);
-
-			conf.close();
-		}
-
-		g_primitives = m_prims;
-		PrimitiveCategory::populateCategories();
-		print ("%1 primitives scanned", g_primitives.size());
-		g_activeScanner = null;
-		emit workDone();
-		deleteLater();
-	}
-	else
-	{
-		// Defer to event loop, pick up the work later
-		emit update (m_i);
-		QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection);
-	}
-}
-
-// =============================================================================
-//
-void PrimitiveScanner::start()
-{
-	if (g_activeScanner)
-		return;
-
-	PrimitiveScanner* scanner = new PrimitiveScanner;
-	scanner->work();
-}
-
-// =============================================================================
-//
-PrimitiveCategory::PrimitiveCategory (QString name, QObject* parent) :
-	QObject (parent),
-	m_name (name) {}
-
-// =============================================================================
-//
-void PrimitiveCategory::populateCategories()
-{
-	for (PrimitiveCategory* cat : g_PrimitiveCategories)
-		cat->prims.clear();
-
-
-	for (Primitive& prim : g_primitives)
-	{
-		bool matched = false;
-		prim.category = null;
-
-		// Go over the categories and their regexes, if and when there's a match,
-		// the primitive's category is set to the category the regex beloings to.
-		for (PrimitiveCategory* cat : g_PrimitiveCategories)
-		{
-			for (RegexEntry& entry : cat->regexes)
-			{
-				switch (entry.type)
-				{
-					case EFilenameRegex:
-					{
-						// f-regex, check against filename
-						matched = entry.regex.exactMatch (prim.name);
-					} break;
-
-					case ETitleRegex:
-					{
-						// t-regex, check against title
-						matched = entry.regex.exactMatch (prim.title);
-					} break;
-				}
-
-				if (matched)
-				{
-					prim.category = cat;
-					break;
-				}
-			}
-
-			// Drop out if a category was decided on.
-			if (prim.category != null)
-				break;
-		}
-
-		// If there was a match, add the primitive to the category.
-		// Otherwise, add it to the list of unmatched primitives.
-		if (prim.category != null)
-			prim.category->prims << prim;
-		else
-			g_unmatched->prims << prim;
-	}
-}
-
-// =============================================================================
-//
-void PrimitiveCategory::loadCategories()
-{
-	for (PrimitiveCategory* cat : g_PrimitiveCategories)
-		delete cat;
-
-	g_PrimitiveCategories.clear();
-	QString path = Config::dirpath() + "primregexps.cfg";
-
-	if (!QFile::exists (path))
-		path = ":/data/primitive-categories.cfg";
-
-	QFile f (path);
-
-	if (!f.open (QIODevice::ReadOnly))
-	{
-		critical (format (QObject::tr ("Failed to open primitive categories: %1"), f.errorString()));
-		return;
-	}
-
-	PrimitiveCategory* cat = null;
-
-	while (f.atEnd() == false)
-	{
-		QString line = f.readLine();
-		int colon;
-
-		if (line.endsWith ("\n"))
-			line.chop (1);
-
-		if (line.length() == 0 || line[0] == '#')
-			continue;
-
-		if ((colon = line.indexOf (":")) == -1)
-		{
-			if (cat && cat->isValidToInclude())
-				g_PrimitiveCategories << cat;
-
-			cat = new PrimitiveCategory (line);
-		}
-		elif (cat != null)
-		{
-			QString cmd = line.left (colon);
-			RegexType type = EFilenameRegex;
-
-			if (cmd == "f")
-				type = EFilenameRegex;
-			elif (cmd == "t")
-				type = ETitleRegex;
-			else
-			{
-				print (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line);
-				continue;
-			}
-
-			QRegExp regex (line.mid (colon + 1));
-			RegexEntry entry = { regex, type };
-			cat->regexes << entry;
-		}
-		else
-			print ("Warning: Rules given before the first category name");
-	}
-
-	if (cat->isValidToInclude())
-		g_PrimitiveCategories << cat;
-
-	// Add a category for unmatched primitives.
-	// Note: if this function is called the second time, g_unmatched has been
-	// deleted at the beginning of the function and is dangling at this point.
-	g_unmatched = new PrimitiveCategory (tr ("Other"));
-	g_PrimitiveCategories << g_unmatched;
-	f.close();
-}
-
-// =============================================================================
-//
-bool PrimitiveCategory::isValidToInclude()
-{
-	if (regexes.isEmpty())
-	{
-		print (tr ("Warning: category \"%1\" left without patterns"), name());
-		deleteLater();
-		return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-bool isPrimitiveLoaderBusy()
-{
-	return g_activeScanner != null;
-}
-
-// =============================================================================
-//
-static double radialPoint (int i, int divs, double (*func) (double))
-{
-	return (*func) ((i * 2 * pi) / divs);
-}
-
-// =============================================================================
-//
-void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines)
-{
-	for (int i = 0; i < segs; ++i)
-	{
-		double x0 = radius * radialPoint (i, divs, cos),
-			x1 = radius * radialPoint (i + 1, divs, cos),
-			z0 = radius * radialPoint (i, divs, sin),
-			z1 = radius * radialPoint (i + 1, divs, sin);
-
-		lines << QLineF (QPointF (x0, z0), QPointF (x1, z1));
-	}
-}
-
-// =============================================================================
-//
-LDObjectList makePrimitive (PrimitiveType type, int segs, int divs, int num)
-{
-	LDObjectList objs;
-	QList<int> condLineSegs;
-	QList<QLineF> circle;
-
-	makeCircle (segs, divs, 1, circle);
-
-	for (int i = 0; i < segs; ++i)
-	{
-		double x0 = circle[i].x1(),
-				   x1 = circle[i].x2(),
-				   z0 = circle[i].y1(),
-				   z1 = circle[i].y2();
-
-		switch (type)
-		{
-			case Circle:
-			{
-				Vertex v0 (x0, 0.0f, z0),
-				  v1 (x1, 0.0f, z1);
-
-				LDLine* line = new LDLine;
-				line->setVertex (0, v0);
-				line->setVertex (1, v1);
-				line->setColor (edgecolor);
-				objs << line;
-			} break;
-
-			case Cylinder:
-			case Ring:
-			case Cone:
-			{
-				double x2, x3, z2, z3;
-				double y0, y1, y2, y3;
-
-				if (type == Cylinder)
-				{
-					x2 = x1;
-					x3 = x0;
-					z2 = z1;
-					z3 = z0;
-
-					y0 = y1 = 0.0f;
-					y2 = y3 = 1.0f;
-				}
-				else
-				{
-					x2 = x1 * (num + 1);
-					x3 = x0 * (num + 1);
-					z2 = z1 * (num + 1);
-					z3 = z0 * (num + 1);
-
-					x0 *= num;
-					x1 *= num;
-					z0 *= num;
-					z1 *= num;
-
-					if (type == Ring)
-						y0 = y1 = y2 = y3 = 0.0f;
-					else
-					{
-						y0 = y1 = 1.0f;
-						y2 = y3 = 0.0f;
-					}
-				}
-
-				Vertex v0 (x0, y0, z0),
-					   v1 (x1, y1, z1),
-					   v2 (x2, y2, z2),
-					   v3 (x3, y3, z3);
-
-				LDQuad* quad = new LDQuad;
-				quad->setColor (maincolor);
-				quad->setVertex (0, v0);
-				quad->setVertex (1, v1);
-				quad->setVertex (2, v2);
-				quad->setVertex (3, v3);
-
-				if (type == Cylinder)
-					quad->invert();
-
-				objs << quad;
-
-				if (type == Cylinder || type == Cone)
-					condLineSegs << i;
-			} break;
-
-			case Disc:
-			case DiscNeg:
-			{
-				double x2, z2;
-
-				if (type == Disc)
-					x2 = z2 = 0.0f;
-				else
-				{
-					x2 = (x0 >= 0.0f) ? 1.0f : -1.0f;
-					z2 = (z0 >= 0.0f) ? 1.0f : -1.0f;
-				}
-
-				Vertex v0 (x0, 0.0f, z0),
-					   v1 (x1, 0.0f, z1),
-					   v2 (x2, 0.0f, z2);
-
-				// Disc negatives need to go the other way around, otherwise
-				// they'll end up upside-down.
-				LDTriangle* seg = new LDTriangle;
-				seg->setColor (maincolor);
-				seg->setVertex (type == Disc ? 0 : 2, v0);
-				seg->setVertex (1, v1);
-				seg->setVertex (type == Disc ? 2 : 0, v2);
-				objs << seg;
-			} break;
-		}
-	}
-
-	// If this is not a full circle, we need a conditional line at the other
-	// end, too.
-	if (segs < divs && condLineSegs.size() != 0)
-		condLineSegs << segs;
-
-	for (int i : condLineSegs)
-	{
-		Vertex v0 (radialPoint (i, divs, cos), 0.0f, radialPoint (i, divs, sin)),
-		  v1,
-		  v2 (radialPoint (i + 1, divs, cos), 0.0f, radialPoint (i + 1, divs, sin)),
-		  v3 (radialPoint (i - 1, divs, cos), 0.0f, radialPoint (i - 1, divs, sin));
-
-		if (type == Cylinder)
-			v1 = Vertex (v0[X], 1.0f, v0[Z]);
-		elif (type == Cone)
-		{
-			v1 = Vertex (v0[X] * (num + 1), 0.0f, v0[Z] * (num + 1));
-			v0[X] *= num;
-			v0[Y] = 1.0f;
-			v0[Z] *= num;
-		}
-
-		LDCondLine* line = new LDCondLine;
-		line->setColor (edgecolor);
-		line->setVertex (0, v0);
-		line->setVertex (1, v1);
-		line->setVertex (2, v2);
-		line->setVertex (3, v3);
-		objs << line;
-	}
-
-	return objs;
-}
-
-// =============================================================================
-//
-static QString primitiveTypeName (PrimitiveType type)
-{
-	// Not translated as primitives are in English.
-	return type == Circle   ? "Circle" :
-		   type == Cylinder ? "Cylinder" :
-		   type == Disc     ? "Disc" :
-		   type == DiscNeg  ? "Disc Negative" :
-		   type == Ring     ? "Ring" : "Cone";
-}
-
-// =============================================================================
-//
-QString radialFileName (PrimitiveType type, int segs, int divs, int num)
-{
-	int numer = segs,
-			denom = divs;
-
-	// Simplify the fractional part, but the denominator must be at least 4.
-	simplify (numer, denom);
-
-	if (denom < 4)
-	{
-		const int factor = 4 / denom;
-		numer *= factor;
-		denom *= factor;
-	}
-
-	// Compose some general information: prefix, fraction, root, ring number
-	QString prefix = (divs == g_lores) ? "" : format ("%1/", divs);
-	QString frac = format ("%1-%2", numer, denom);
-	QString root = g_radialNameRoots[type];
-	QString numstr = (type == Ring || type == Cone) ? format ("%1", num) : "";
-
-	// Truncate the root if necessary (7-16rin4.dat for instance).
-	// However, always keep the root at least 2 characters.
-	int extra = (frac.length() + numstr.length() + root.length()) - 8;
-	root.chop (clamp (extra, 0, 2));
-
-	// Stick them all together and return the result.
-	return prefix + frac + root + numstr + ".dat";
-}
-
-// =============================================================================
-//
-LDDocument* generatePrimitive (PrimitiveType type, int segs, int divs, int num)
-{
-	// Make the description
-	QString frac = QString::number ((float) segs / divs);
-	QString name = radialFileName (type, segs, divs, num);
-	QString descr;
-
-	// Ensure that there's decimals, even if they're 0.
-	if (frac.indexOf (".") == -1)
-		frac += ".0";
-
-	if (type == Ring || type == Cone)
-	{
-		QString spacing =
-			(num < 10) ? "  " :
-			(num < 100) ? " "  : "";
-
-		descr = format ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac);
-	}
-	else
-		descr = format ("%1 %2", primitiveTypeName (type), frac);
-
-	// Prepend "Hi-Res" if 48/ primitive.
-	if (divs == g_hires)
-		descr.insert (0, "Hi-Res ");
-
-	LDDocument* f = new LDDocument;
-	f->setDefaultName (name);
-
-	QString author = APPNAME;
-	QString license = "";
-
-	if (ld_defaultname.isEmpty() == false)
-	{
-		license = getLicenseText (ld_defaultlicense);
-		author = format ("%1 [%2]", ld_defaultname, ld_defaultuser);
-	}
-
-	f->addObjects (
-	{
-		new LDComment (descr),
-		new LDComment (format ("Name: %1", name)),
-		new LDComment (format ("Author: %1", author)),
-		new LDComment (format ("!LDRAW_ORG Unofficial_%1Primitive", divs == g_hires ? "48_" : "")),
-		new LDComment (license),
-		new LDEmpty,
-		new LDBFC (LDBFC::CertifyCCW),
-		new LDEmpty,
-	});
-
-	f->addObjects (makePrimitive (type, segs, divs, num));
-	return f;
-}
-
-// =============================================================================
-//
-LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num)
-{
-	QString name = radialFileName (type, segs, divs, num);
-	LDDocument* f = getDocument (name);
-
-	if (f != null)
-		return f;
-
-	return generatePrimitive (type, segs, divs, num);
-}
-
-// =============================================================================
-//
-PrimitivePrompt::PrimitivePrompt (QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f)
-{
-	ui = new Ui_MakePrimUI;
-	ui->setupUi (this);
-	connect (ui->cb_hires, SIGNAL (toggled (bool)), this, SLOT (hiResToggled (bool)));
-}
-
-// =============================================================================
-//
-PrimitivePrompt::~PrimitivePrompt()
-{
-	delete ui;
-}
-
-// =============================================================================
-//
-void PrimitivePrompt::hiResToggled (bool on)
-{
-	ui->sb_segs->setMaximum (on ? g_hires : g_lores);
-
-	// If the current value is 16 and we switch to hi-res, default the
-	// spinbox to 48.
-	if (on && ui->sb_segs->value() == g_lores)
-		ui->sb_segs->setValue (g_hires);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (MakePrimitive, 0)
-{
-	PrimitivePrompt* dlg = new PrimitivePrompt (g_win);
-
-	if (!dlg->exec())
-		return;
-
-	int segs = dlg->ui->sb_segs->value();
-	int divs = dlg->ui->cb_hires->isChecked() ? g_hires : g_lores;
-	int num = dlg->ui->sb_ringnum->value();
-	PrimitiveType type =
-		dlg->ui->rb_circle->isChecked()   ? Circle :
-		dlg->ui->rb_cylinder->isChecked() ? Cylinder :
-		dlg->ui->rb_disc->isChecked()     ? Disc :
-		dlg->ui->rb_ndisc->isChecked()    ? DiscNeg :
-		dlg->ui->rb_ring->isChecked()     ? Ring : Cone;
-
-	LDDocument* f = generatePrimitive (type, segs, divs, num);
-
-	g_win->save (f, false);
-	delete f;
-}
--- a/src/Primitives.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,130 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include "Main.h"
-#include "Types.h"
-#include <QRegExp>
-#include <QDialog>
-
-class LDDocument;
-class Ui_MakePrimUI;
-class PrimitiveCategory;
-struct Primitive
-{
-	QString				name,
-						title;
-	PrimitiveCategory*	category;
-};
-
-class PrimitiveCategory : public QObject
-{
-	Q_OBJECT
-	PROPERTY (public, QString, name, setName, STOCK_WRITE)
-
-	public:
-		enum RegexType
-		{
-			EFilenameRegex,
-			ETitleRegex
-		};
-
-		struct RegexEntry
-		{
-			QRegExp		regex;
-			RegexType	type;
-		};
-
-		QList<RegexEntry> regexes;
-		QList<Primitive> prims;
-
-		explicit PrimitiveCategory (QString name, QObject* parent = 0);
-		bool isValidToInclude();
-
-		static void loadCategories();
-		static void populateCategories();
-};
-
-// =============================================================================
-//
-// PrimitiveScanner
-//
-// Worker object that scans the primitives folder for primitives and
-// builds an index of them.
-//
-class PrimitiveScanner : public QObject
-{
-	Q_OBJECT
-
-	public:
-		explicit			PrimitiveScanner (QObject* parent = 0);
-		virtual				~PrimitiveScanner();
-		static void			start();
-
-	public slots:
-		void				work();
-
-	signals:
-		void				starting (int num);
-		void				workDone();
-		void				update (int i);
-
-	private:
-		QList<Primitive>	m_prims;
-		QStringList			m_files;
-		int					m_i;
-		int					m_baselen;
-};
-
-extern QList<PrimitiveCategory*> g_PrimitiveCategories;
-
-void loadPrimitives();
-PrimitiveScanner* getActivePrimitiveScanner();
-
-enum PrimitiveType
-{
-	Circle,
-	Cylinder,
-	Disc,
-	DiscNeg,
-	Ring,
-	Cone,
-};
-
-// =============================================================================
-class PrimitivePrompt : public QDialog
-{
-	Q_OBJECT
-
-	public:
-		explicit PrimitivePrompt (QWidget* parent = null, Qt::WindowFlags f = 0);
-		virtual ~PrimitivePrompt();
-		Ui_MakePrimUI* ui;
-
-	public slots:
-		void hiResToggled (bool on);
-};
-
-void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines);
-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.
-LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num);
-
-QString radialFileName (PrimitiveType type, int segs, int divs, int num);
--- a/src/Types.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,403 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QObject>
-#include <QStringList>
-#include <QTextStream>
-#include <QFile>
-#include <assert.h>
-#include "Main.h"
-#include "Types.h"
-#include "Misc.h"
-#include "LDObject.h"
-#include "Document.h"
-
-// =============================================================================
-//
-Vertex::Vertex (double x, double y, double z)
-{
-	m_coords[X] = x;
-	m_coords[Y] = y;
-	m_coords[Z] = z;
-}
-
-// =============================================================================
-//
-void Vertex::move (const Vertex& other)
-{
-	for_axes (ax)
-		m_coords[ax] += other[ax];
-}
-
-// =============================================================================
-//
-double Vertex::distanceTo (const Vertex& other) const
-{
-	double dx = abs (x() - other.x());
-	double dy = abs (y() - other.y());
-	double dz = abs (z() - other.z());
-	return sqrt ((dx * dx) + (dy * dy) + (dz * dz));
-}
-
-// =============================================================================
-//
-Vertex Vertex::midpoint (const Vertex& other)
-{
-	Vertex mid;
-
-	for_axes (ax)
-		mid[ax] = (getCoordinate (ax) + other[ax]) / 2;
-
-	return mid;
-}
-
-// =============================================================================
-//
-QString Vertex::toString (bool mangled) const
-{
-	QString formatstr = "%1 %2 %3";
-
-	if (mangled)
-		formatstr = "(%1, %2, %3)";
-
-	return format (formatstr, x(), y(), z());
-}
-
-// =============================================================================
-//
-void Vertex::transform (const Matrix& matr, const Vertex& pos)
-{
-	double x2 = (matr[0] * x()) + (matr[1] * y()) + (matr[2] * z()) + pos[X];
-	double y2 = (matr[3] * x()) + (matr[4] * y()) + (matr[5] * z()) + pos[Y];
-	double z2 = (matr[6] * x()) + (matr[7] * y()) + (matr[8] * z()) + pos[Z];
-
-	x() = x2;
-	y() = y2;
-	z() = z2;
-}
-
-// =============================================================================
-//
-Vertex Vertex::operator-() const
-{
-	return Vertex (-m_coords[X], -m_coords[Y], -m_coords[Z]);
-}
-
-// =============================================================================
-//
-bool Vertex::operator!= (const Vertex& other) const
-{
-	return !operator== (other);
-}
-
-// =============================================================================
-//
-bool Vertex::operator== (const Vertex& other) const
-{
-	return getCoordinate (X) == other[X] &&
-		   getCoordinate (Y) == other[Y] &&
-		   getCoordinate (Z) == other[Z];
-}
-
-// =============================================================================
-//
-Vertex& Vertex::operator/= (const double d)
-{
-	for_axes (ax)
-		m_coords[ax] /= d;
-
-	return *this;
-}
-
-// =============================================================================
-//
-Vertex Vertex::operator/ (const double d) const
-{
-	Vertex other (*this);
-	return other /= d;
-}
-
-// =============================================================================
-//
-Vertex& Vertex::operator+= (const Vertex& other)
-{
-	move (other);
-	return *this;
-}
-
-// =============================================================================
-//
-Vertex Vertex::operator+ (const Vertex& other) const
-{
-	Vertex newvert (*this);
-	newvert.move (other);
-	return newvert;
-}
-
-// =============================================================================
-//
-int Vertex::operator< (const Vertex& other) const
-{
-	if (operator== (other))
-		return false;
-
-	if (getCoordinate (X) < other[X])
-		return true;
-
-	if (getCoordinate (X) > other[X])
-		return false;
-
-	if (getCoordinate (Y) < other[Y])
-		return true;
-
-	if (getCoordinate (Y) > other[Y])
-		return false;
-
-	return getCoordinate (Z) < other[Z];
-}
-
-// =============================================================================
-//
-Matrix::Matrix (double vals[])
-{
-	for (int i = 0; i < 9; ++i)
-		m_vals[i] = vals[i];
-}
-
-// =============================================================================
-//
-Matrix::Matrix (double fillval)
-{
-	for (int i = 0; i < 9; ++i)
-		m_vals[i] = fillval;
-}
-
-// =============================================================================
-//
-Matrix::Matrix (const std::initializer_list< double >& vals)
-{
-	assert (vals.size() == 9);
-	memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals);
-}
-
-// =============================================================================
-//
-void Matrix::dump() const
-{
-	for (int i = 0; i < 3; ++i)
-	{
-		for (int j = 0; j < 3; ++j)
-			print ("%1\t", m_vals[ (i * 3) + j]);
-
-		print ("\n");
-	}
-}
-
-// =============================================================================
-//
-QString Matrix::toString() const
-{
-	QString val;
-
-	for (int i = 0; i < 9; ++i)
-	{
-		if (i > 0)
-			val += ' ';
-
-		val += QString::number (m_vals[i]);
-	}
-
-	return val;
-}
-
-// =============================================================================
-//
-void Matrix::zero()
-{
-	memset (&m_vals[0], 0, sizeof m_vals);
-}
-
-// =============================================================================
-//
-Matrix Matrix::mult (const Matrix& other) const
-{
-	Matrix val;
-	val.zero();
-
-	for (int i = 0; i < 3; ++i)
-	for (int j = 0; j < 3; ++j)
-	for (int k = 0; k < 3; ++k)
-		val[(i * 3) + j] += m_vals[(i * 3) + k] * other[(k * 3) + j];
-
-	return val;
-}
-
-// =============================================================================
-//
-Matrix& Matrix::operator= (const Matrix& other)
-{
-	memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals);
-	return *this;
-}
-
-// =============================================================================
-//
-double Matrix::getDeterminant() const
-{
-	return (value (0) * value (4) * value (8)) +
-		   (value (1) * value (5) * value (6)) +
-		   (value (2) * value (3) * value (7)) -
-		   (value (2) * value (4) * value (6)) -
-		   (value (1) * value (3) * value (8)) -
-		   (value (0) * value (5) * value (7));
-}
-
-// =============================================================================
-//
-bool Matrix::operator== (const Matrix& other) const
-{
-	for (int i = 0; i < 9; ++i)
-		if (value (i) != other[i])
-			return false;
-
-	return true;
-}
-
-// =============================================================================
-//
-LDBoundingBox::LDBoundingBox()
-{
-	reset();
-}
-
-// =============================================================================
-//
-void LDBoundingBox::calculateFromCurrentDocument()
-{
-	reset();
-
-	if (!getCurrentDocument())
-		return;
-
-	for (LDObject* obj : getCurrentDocument()->objects())
-		calcObject (obj);
-}
-
-// =============================================================================
-//
-void LDBoundingBox::calcObject (LDObject* obj)
-{
-	switch (obj->type())
-	{
-		case LDObject::ELine:
-		case LDObject::ETriangle:
-		case LDObject::EQuad:
-		case LDObject::ECondLine:
-		{
-			for (int i = 0; i < obj->vertices(); ++i)
-				calcVertex (obj->vertex (i));
-		} break;
-
-		case LDObject::ESubfile:
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline);
-
-			for (LDObject * obj : objs)
-			{
-				calcObject (obj);
-				obj->destroy();
-			}
-		}
-		break;
-
-		default:
-			break;
-	}
-}
-
-// =============================================================================
-//
-LDBoundingBox& LDBoundingBox::operator<< (const Vertex& v)
-{
-	calcVertex (v);
-	return *this;
-}
-
-// =============================================================================
-//
-LDBoundingBox& LDBoundingBox::operator<< (LDObject* obj)
-{
-	calcObject (obj);
-	return *this;
-}
-
-// =============================================================================
-//
-void LDBoundingBox::calcVertex (const Vertex& vertex)
-{
-	for_axes (ax)
-	{
-		m_vertex0[ax] = min (vertex[ax], m_vertex0[ax]);
-		m_vertex1[ax] = max (vertex[ax], m_vertex1[ax]);
-	}
-
-	setEmpty (false);
-}
-
-// =============================================================================
-//
-void LDBoundingBox::reset()
-{
-	m_vertex0[X] = m_vertex0[Y] = m_vertex0[Z] = 10000.0;
-	m_vertex1[X] = m_vertex1[Y] = m_vertex1[Z] = -10000.0;
-	setEmpty (true);
-}
-
-// =============================================================================
-//
-double LDBoundingBox::longestMeasurement() const
-{
-	double xscale = (m_vertex0[X] - m_vertex1[X]);
-	double yscale = (m_vertex0[Y] - m_vertex1[Y]);
-	double zscale = (m_vertex0[Z] - m_vertex1[Z]);
-	double size = zscale;
-
-	if (xscale > yscale)
-	{
-		if (xscale > zscale)
-			size = xscale;
-	}
-	elif (yscale > zscale)
-		size = yscale;
-
-	if (abs (size) >= 2.0f)
-		return abs (size / 2);
-
-	return 1.0f;
-}
-
-// =============================================================================
-//
-Vertex LDBoundingBox::center() const
-{
-	return Vertex (
-		(m_vertex0[X] + m_vertex1[X]) / 2,
-		(m_vertex0[Y] + m_vertex1[Y]) / 2,
-		(m_vertex0[Z] + m_vertex1[Z]) / 2);
-}
--- a/src/Types.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,335 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QString>
-#include <QObject>
-#include <QStringList>
-#include <QMetaType>
-#include "Macros.h"
-
-class LDObject;
-class QFile;
-class QTextStream;
-
-using int8 = qint8;
-using int16 = qint16;
-using int32 = qint32;
-using int64 = qint64;
-using uint8 = quint8;
-using uint16 = quint16;
-using uint32 = quint32;
-using uint64 = quint64;
-
-template<typename T, typename R>
-using Pair = std::pair<T, R>;
-
-enum Axis
-{
-	X,
-	Y,
-	Z
-};
-
-// =============================================================================
-//
-class LDObject;
-using LDObjectList = QList<LDObject*>;
-
-//!
-//! \brief A mathematical 3 x 3 matrix
-//!
-class Matrix
-{
-	public:
-		//! Constructs a matrix with undetermined values.
-		Matrix() {}
-
-		//! Constructs a matrix with the given values.
-		//! \note \c vals is expected to have exactly 9 elements.
-		Matrix (const std::initializer_list<double>& vals);
-
-		//! Constructs a matrix all 9 elements initialized to the same value.
-		//! \param fillval the value to initialize the matrix coordinates as
-		Matrix (double fillval);
-
-		//! Constructs a matrix with a C-array.
-		//! \note \c vals is expected to have exactly 9 elements.
-		Matrix (double vals[]);
-
-		//! Calculates the matrix's determinant.
-		//! \returns the calculated determinant.
-		double			getDeterminant() const;
-
-		//! Multiplies this matrix with \c other
-		//! \param other the matrix to multiply with.
-		//! \returns the resulting matrix
-		//! \note a.mult(b) is not equivalent to b.mult(a)!
-		Matrix			mult (const Matrix& other) const;
-
-		//! Prints the matrix to stdout.
-		void			dump() const;
-
-		//! \returns a string representation of the matrix.
-		QString			toString() const;
-
-		//! Zeroes the matrix out.
-		void			zero();
-
-		//! Assigns the matrix values to the values of \c other.
-		//! \param other the matrix to assign this to.
-		//! \returns a reference to self
-		Matrix&			operator= (const Matrix& other);
-
-		//! \returns a mutable reference to a value by \c idx
-		inline double& value (int idx)
-		{
-			return m_vals[idx];
-		}
-
-		//! An overload of \c value() for const matrices.
-		//! \returns a const reference to a value by \c idx
-		inline const double& value (int idx) const
-		{
-			return m_vals[idx];
-		}
-
-		//! An operator overload for \c mult().
-		//! \returns the multiplied matrix.
-		inline Matrix operator* (const Matrix& other) const
-		{
-			return mult (other);
-		}
-
-		//! An operator overload for \c value().
-		//! \returns a mutable reference to a value by \c idx
-		inline double& operator[] (int idx)
-		{
-			return value (idx);
-		}
-
-		//! An operator overload for \c value() const.
-		//! \returns a const reference to a value by \c idx
-		inline const double& operator[] (int idx) const
-		{
-			return value (idx);
-		}
-
-		//! \param other the matrix to check against
-		//! \returns whether the two matrices have the same values.
-		bool operator== (const Matrix& other) const;
-
-	private:
-		double m_vals[9];
-};
-
-//!
-//! \brief A vertex in 3D space
-//!
-//! Contains a single point in 3D space. Not to be confused with
-//! LDVertex, which is a vertex used in an LDraw part file.
-//!
-//! This also sees use as a position vector.
-//!
-class Vertex
-{
-	public:
-		//! Constructs a zero vertex
-		Vertex() :
-			m_coords{0, 0, 0} {}
-
-		//! Constructs a vertex with the given \c x, \c y and \c z.
-		Vertex (double x, double y, double z);
-
-		//! \returns the distance from this vertex to \c other
-		double			distanceTo (const Vertex& other) const;
-
-		//! \returns the vertex at the midpoint between this and \c other
-		Vertex			midpoint (const Vertex& other);
-
-		//! Moves this vertex using \param other as a position vector.
-		void			move (const Vertex& other);
-
-		//! Yields a string representation of the vertex. The string returned
-		//! can possibly be mangled.
-		//! - As mangled: {1.5, 2.8, 3.14}
-		//! - Without mangling: 1.5 2.8 3.14
-		//!
-		//! The mangled version is suitable for printing to the user, the
-		//! non-mangled one is used when writing the vertex to LDraw files.
-		//!
-		//! \returns a string representation of this vertex
-		//! \param mangled whether to return a mangled representation or not
-		QString			toString (bool mangled) const;
-
-		//! Transforms this vertex with \c matr as transformation matrix
-		//! and \c pos as the position column of the 4x4 matrix.
-		void			transform (const Matrix& matr, const Vertex& pos);
-
-		//! An operator overload for \c move().
-		Vertex&			operator+= (const Vertex& other);
-
-		//! An operator overload for \c move(), using a temporary vertex.
-		Vertex			operator+ (const Vertex& other) const;
-
-		//! Divides all values by \c d.
-		Vertex			operator/ (const double d) const;
-
-		//! Divides all values by \c d.
-		Vertex&			operator/= (const double d);
-
-		//! Checks whether this vertex has the same values as \c other.
-		bool			operator== (const Vertex& other) const;
-
-		//! Checks whether this vertex has different values than \c other.
-		bool			operator!= (const Vertex& other) const;
-
-		//! \returns a negated version the vertex
-		Vertex			operator-() const;
-
-		//! \returns whether the vertex has lesser values than \c other.
-		int				operator< (const Vertex& other) const;
-
-		//! An operator overload for \c getCoordinate().
-		inline double& operator[] (const Axis ax)
-		{
-			return getCoordinate ((int) ax);
-		}
-
-		//! An operator overload for \c getCoordinate() const.
-		inline const double& operator[] (const Axis ax) const
-		{
-			return getCoordinate ((int) ax);
-		}
-
-		//! An operator overload for \c getCoordinate().
-		inline double& operator[] (const int ax)
-		{
-			return getCoordinate (ax);
-		}
-
-		//! An operator overload for \c getCoordinate() const.
-		inline const double& operator[] (const int ax) const
-		{
-			return getCoordinate (ax);
-		}
-
-		//! \returns a mutable reference for the coordinate designated by \param n.
-		inline double& getCoordinate (int n)
-		{
-			return m_coords[n];
-		}
-
-		//! An overload of \c getCoordinate for const vertices.
-		//! \returns a const reference for the coordinate designated by \param n.
-		inline const double& getCoordinate (int n) const
-		{
-			return m_coords[n];
-		}
-
-		//! \returns a mutable reference to X.
-		inline double& x()
-		{
-			return m_coords[X];
-		}
-
-		//! An overload of \c x() for const vertices.
-		//! \returns a const reference to X.
-		inline const double& x() const
-		{
-			return m_coords[X];
-		}
-
-		//! \returns a mutable reference to Y.
-		inline double& y()
-		{
-			return m_coords[Y];
-		}
-
-		//! An overload of \c y() for const vertices.
-		//! \returns a const reference to Y.
-		inline const double& y() const
-		{
-			return m_coords[Y];
-		}
-
-		//! \returns a mutable reference to Z.
-		inline double& z()
-		{
-			return m_coords[Z];
-		}
-
-		//! An overload of \c z() for const vertices.
-		//! \returns a const reference to Z.
-		inline const double& z() const
-		{
-			return m_coords[Z];
-		}
-
-	private:
-		double m_coords[3];
-};
-
-Q_DECLARE_METATYPE (Vertex)
-
-//!
-//! Defines a bounding box that encompasses a given set of objects.
-//! vertex0 is the minimum vertex, vertex1 is the maximum vertex.
-//
-class LDBoundingBox
-{
-	PROPERTY (private,	bool,		isEmpty,	setEmpty,		STOCK_WRITE)
-	PROPERTY (private,	Vertex,		vertex0,	setVertex0,		STOCK_WRITE)
-	PROPERTY (private,	Vertex,		vertex1,	setVertex1,		STOCK_WRITE)
-
-	public:
-		//! Constructs an empty bounding box.
-		LDBoundingBox();
-
-		//! Clears the bounding box
-		void reset();
-
-		//! Calculates the bounding box's values from the objects in the current
-		//! document.
-		void calculateFromCurrentDocument();
-
-		//! \returns the length of the bounding box on the longest measure.
-		double longestMeasurement() const;
-
-		//! Calculates the given \c obj to the bounding box, adjusting
-		//! extremas if necessary.
-		void calcObject (LDObject* obj);
-
-		//! Calculates the given \c vertex to the bounding box, adjusting
-		//! extremas if necessary.
-		void calcVertex (const Vertex& vertex);
-
-		//! \returns the center of the bounding box.
-		Vertex center() const;
-
-		//! An operator overload for \c calcObject()
-		LDBoundingBox& operator<< (LDObject* obj);
-
-		//! An operator overload for \c calcVertex()
-		LDBoundingBox& operator<< (const Vertex& v);
-};
-
-extern const Vertex g_origin; // Vertex at (0, 0, 0)
-extern const Matrix g_identity; // Identity matrix
-
-static const double pi = 3.14159265358979323846;
--- a/src/Version.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <string.h>
-#include "Version.h"
-#include "Git.h"
-
-char gVersionString[64] = {'\0'};
-char gFullVersionString[256] = {'\0'};
-
-// =============================================================================
-//
-const char* versionString()
-{
-	if (gVersionString[0] == '\0')
-	{
-#if VERSION_PATCH == 0
-		sprintf (gVersionString, "%d.%d", VERSION_MAJOR, VERSION_MINOR);
-#else
-		sprintf (gVersionString, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
-#endif // VERSION_PATCH
-	}
-
-	return gVersionString;
-}
-
-// =============================================================================
-//
-const char* fullVersionString()
-{
-	if (gFullVersionString[0] == '\0')
-	{
-#if BUILD_ID != BUILD_RELEASE
-		strcpy (gFullVersionString, GIT_DESCRIPTION);
-#else
-		sprintf (gFullVersionString, "v%s", versionString());
-#endif
-	}
-
-	return gFullVersionString;
-}
--- a/src/Version.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-//! \file Version.h
-//! Contains macros related to application name and version.
-
-#pragma once
-
-//! The application name.
-#define APPNAME			"LDForge"
-
-//! The unix-style name of the application. used in filenames
-#define UNIXNAME		"ldforge"
-
-//! The major version number.
-#define VERSION_MAJOR	0
-
-//! The minor version number.
-#define VERSION_MINOR	3
-
-//! The patch level version number.
-#define VERSION_PATCH	0
-
-//! The build ID, use either BUILD_INTERNAL or BUILD_RELEASE
-#define BUILD_ID		BUILD_INTERNAL
-
-//! The build code for internal builds
-#define BUILD_INTERNAL	0
-
-//! The build code for release builds.
-#define BUILD_RELEASE	1
-
-// =============================================
-#ifdef DEBUG
-# undef RELEASE
-#endif // DEBUG
-
-#ifdef RELEASE
-# undef DEBUG
-#endif // RELEASE
--- a/src/Widgets.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-// I still find the radio group useful... find a way to use this in Designer.
-// I probably need to look into how to make Designer plugins.
-// TODO: try make this usable in Designer
-
-#include <QBoxLayout>
-#include <QRadioButton>
-#include <QButtonGroup>
-#include <QCheckBox>
-#include <map>
-
-#include "Widgets.h"
-
-// =============================================================================
-//
-RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent)
-{
-	init (Qt::Vertical);
-}
-
-// =============================================================================
-//
-QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false)
-{
-	return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom;
-}
-
-// =============================================================================
-//
-bool RadioGroup::isChecked (int n) const
-{
-	return m_buttonGroup->checkedId() == n;
-}
-
-// =============================================================================
-//
-void RadioGroup::init (Qt::Orientation orient)
-{
-	m_vert = orient == Qt::Vertical;
-
-	m_buttonGroup = new QButtonGroup;
-	m_oldId = m_curId = 0;
-	m_coreLayout = null;
-
-	m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom);
-	setLayout (m_coreLayout);
-
-	// Init the first row with a break
-	rowBreak();
-
-	connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int)));
-	connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int)));
-}
-
-// =============================================================================
-//
-RadioGroup::RadioGroup (const QString& title, QList<char const*> entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) :
-		QGroupBox (title, parent),
-		m_defId (defaultId)
-{
-	init (orient);
-	m_oldId = m_defId;
-
-	for (const char* entry : entries)
-		addButton (entry);
-}
-
-// =============================================================================
-//
-void RadioGroup::rowBreak()
-{
-	QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight);
-	m_currentLayout = newLayout;
-	m_layouts << newLayout;
-
-	m_coreLayout->addLayout (newLayout);
-}
-
-// =============================================================================
-//
-void RadioGroup::addButton (const char* entry)
-{
-	QRadioButton* button = new QRadioButton (entry);
-	addButton (button);
-}
-
-// =============================================================================
-//
-void RadioGroup::addButton (QRadioButton* button)
-{
-	bool const selectThis = (m_curId == m_defId);
-
-	m_objects << button;
-	m_buttonGroup->addButton (button, m_curId++);
-	m_currentLayout->addWidget (button);
-
-	if (selectThis)
-		button->setChecked (true);
-}
-
-// =============================================================================
-//
-RadioGroup& RadioGroup::operator<< (QRadioButton* button)
-{
-	addButton (button);
-	return *this;
-}
-
-// =============================================================================
-//
-RadioGroup& RadioGroup::operator<< (const char* entry)
-{
-	addButton (entry);
-	return *this;
-}
-
-// =============================================================================
-//
-void RadioGroup::setCurrentRow (int row)
-{
-	m_currentLayout = m_layouts[row];
-}
-
-// =============================================================================
-//
-int RadioGroup::value() const
-{
-	return m_buttonGroup->checkedId();
-}
-
-// =============================================================================
-//
-void RadioGroup::setValue (int val)
-{
-	m_buttonGroup->button (val)->setChecked (true);
-}
-
-// =============================================================================
-//
-QRadioButton* RadioGroup::operator[] (int n) const
-{
-	return m_objects[n];
-}
-
-// =============================================================================
-//
-void RadioGroup::slot_buttonPressed (int btn)
-{
-	emit buttonPressed (btn);
-
-	m_oldId = m_buttonGroup->checkedId();
-}
-
-// =============================================================================
-//
-void RadioGroup::slot_buttonReleased (int btn)
-{
-	emit buttonReleased (btn);
-	int newid = m_buttonGroup->checkedId();
-
-	if (m_oldId != newid)
-		emit valueChanged (newid);
-}
-
-// =============================================================================
-//
-RadioGroup::Iterator RadioGroup::begin()
-{
-	return m_objects.begin();
-}
-
-// =============================================================================
-//
-RadioGroup::Iterator RadioGroup::end()
-{
-	return m_objects.end();
-}
--- a/src/Widgets.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QGroupBox>
-#include <QSpinBox>
-#include <map>
-#include "Main.h"
-#include "Types.h"
-
-class QIcon;
-class QCheckBox;
-class QButtonGroup;
-class QBoxLayout;
-class QRadioButton;
-
-// =============================================================================
-// RadioGroup
-//
-// Convenience widget - is a groupbox of radio buttons.
-// =============================================================================
-class RadioGroup : public QGroupBox
-{
-	Q_OBJECT
-
-	public:
-		typedef QList<QRadioButton*>::Iterator Iterator;
-
-		explicit RadioGroup()
-		{
-			init (Qt::Vertical);
-		}
-
-		explicit RadioGroup (QWidget* parent = null) : QGroupBox (parent)
-		{
-			init (Qt::Vertical);
-		}
-
-		explicit RadioGroup (const QString& title, QWidget* parent = null);
-		explicit RadioGroup (const QString& title, QList<char const*> entries, int const defaultId,
-			const Qt::Orientation orient = Qt::Vertical, QWidget* parent = null);
-
-		void            addButton	(const char* entry);
-		void            addButton	(QRadioButton* button);
-		Iterator        begin();
-		Iterator        end();
-		void            init (Qt::Orientation orient);
-		bool            isChecked (int n) const;
-		void            rowBreak();
-		void            setCurrentRow (int row);
-		void            setValue (int val);
-		int             value() const;
-
-		QRadioButton*   operator[] (int n) const;
-		RadioGroup&     operator<< (QRadioButton* button);
-		RadioGroup&     operator<< (const char* entry);
-
-	signals:
-		void buttonPressed (int btn);
-		void buttonReleased (int btn);
-		void valueChanged (int val);
-
-	private:
-		QList<QRadioButton*> m_objects;
-		QList<QBoxLayout*> m_layouts;
-		QBoxLayout* m_coreLayout;
-		QBoxLayout* m_currentLayout;
-		bool m_vert;
-		int m_curId, m_defId, m_oldId;
-		QButtonGroup* m_buttonGroup;
-
-		Q_DISABLE_COPY (RadioGroup)
-
-	private slots:
-		void slot_buttonPressed (int btn);
-		void slot_buttonReleased (int btn);
-};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/actions.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,873 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QTextEdit>
+#include <QBoxLayout>
+#include <QDialogButtonBox>
+#include <QPushButton>
+#include <QInputDialog>
+
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "editHistory.h"
+#include "configDialog.h"
+#include "addObjectDialog.h"
+#include "miscallenous.h"
+#include "glRenderer.h"
+#include "dialogs.h"
+#include "primitives.h"
+#include "radioGroup.h"
+#include "colors.h"
+#include "ui_newpart.h"
+
+extern_cfg (Bool,		gl_wireframe);
+extern_cfg (Bool,		gl_colorbfc);
+extern_cfg (String,	ld_defaultname);
+extern_cfg (String,	ld_defaultuser);
+extern_cfg (Int,		ld_defaultlicense);
+extern_cfg (Bool,		gl_drawangles);
+
+// =============================================================================
+//
+DEFINE_ACTION (New, CTRL_SHIFT (N))
+{
+	QDialog* dlg = new QDialog (g_win);
+	Ui::NewPartUI ui;
+	ui.setupUi (dlg);
+
+	QString authortext = ld_defaultname;
+
+	if (!ld_defaultuser.isEmpty())
+		authortext.append (format (" [%1]", ld_defaultuser));
+
+	ui.le_author->setText (authortext);
+
+	switch (ld_defaultlicense)
+	{
+		case 0:
+			ui.rb_license_ca->setChecked (true);
+			break;
+
+		case 1:
+			ui.rb_license_nonca->setChecked (true);
+			break;
+
+		case 2:
+			ui.rb_license_none->setChecked (true);
+			break;
+
+		default:
+			QMessageBox::warning (null, "Warning",
+				format ("Unknown ld_defaultlicense value %1!", ld_defaultlicense));
+			break;
+	}
+
+	if (dlg->exec() == false)
+		return;
+
+	newFile();
+
+	const LDBFC::Statement BFCType =
+		ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW :
+		ui.rb_bfc_cw->isChecked()  ? LDBFC::CertifyCW : LDBFC::NoCertify;
+
+	const QString license =
+		ui.rb_license_ca->isChecked()    ? g_CALicense :
+		ui.rb_license_nonca->isChecked() ? g_nonCALicense : "";
+
+	getCurrentDocument()->addObjects (
+	{
+		new LDComment (ui.le_title->text()),
+		new LDComment ("Name: <untitled>.dat"),
+		new LDComment (format ("Author: %1", ui.le_author->text())),
+		new LDComment (format ("!LDRAW_ORG Unofficial_Part")),
+		(license != "" ? new LDComment (license) : null),
+		new LDEmpty,
+		new LDBFC (BFCType),
+		new LDEmpty,
+	});
+
+	doFullRefresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (NewFile, CTRL (N))
+{
+	newFile();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Open, CTRL (O))
+{
+	QString name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)");
+
+	if (name.length() == 0)
+		return;
+
+	openMainFile (name);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Save, CTRL (S))
+{
+	save (getCurrentDocument(), false);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SaveAs, CTRL_SHIFT (S))
+{
+	save (getCurrentDocument(), true);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SaveAll, CTRL (L))
+{
+	for (LDDocument* file : g_loadedFiles)
+	{
+		if (file->isImplicit())
+			continue;
+
+		save (file, false);
+	}
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Close, CTRL (W))
+{
+	if (!getCurrentDocument()->isSafeToClose())
+		return;
+
+	delete getCurrentDocument();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (CloseAll, 0)
+{
+	if (!safeToCloseAll())
+		return;
+
+	closeAll();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Settings, 0)
+{
+	(new ConfigDialog)->exec();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SetLDrawPath, 0)
+{
+	(new LDrawPathDialog (true))->exec();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Exit, CTRL (Q))
+{
+	exit (0);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (NewSubfile, 0)
+{
+	AddObjectDialog::staticDialog (LDObject::ESubfile, null);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (NewLine, 0)
+{
+	AddObjectDialog::staticDialog (LDObject::ELine, null);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (NewTriangle, 0)
+{
+	AddObjectDialog::staticDialog (LDObject::ETriangle, null);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (NewQuad, 0)
+{
+	AddObjectDialog::staticDialog (LDObject::EQuad, null);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (NewCLine, 0)
+{
+	AddObjectDialog::staticDialog (LDObject::ECondLine, null);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (NewComment, 0)
+{
+	AddObjectDialog::staticDialog (LDObject::EComment, null);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (NewBFC, 0)
+{
+	AddObjectDialog::staticDialog (LDObject::EBFC, null);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (NewVertex, 0)
+{
+	AddObjectDialog::staticDialog (LDObject::EVertex, null);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Edit, 0)
+{
+	if (selection().size() != 1)
+		return;
+
+	LDObject* obj = selection() [0];
+	AddObjectDialog::staticDialog (obj->type(), obj);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Help, KEY (F1))
+{
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (About, 0)
+{
+	AboutDialog().exec();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (AboutQt, 0)
+{
+	QMessageBox::aboutQt (g_win);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SelectAll, CTRL (A))
+{
+	for (LDObject* obj : getCurrentDocument()->objects())
+		obj->select();
+
+	updateSelection();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A))
+{
+	int colnum = getSelectedColor();
+
+	if (colnum == -1)
+		return; // no consensus on color
+
+	getCurrentDocument()->clearSelection();
+
+	for (LDObject* obj : getCurrentDocument()->objects())
+		if (obj->color() == colnum)
+			obj->select();
+
+	updateSelection();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SelectByType, 0)
+{
+	if (selection().isEmpty())
+		return;
+
+	LDObject::Type type = getUniformSelectedType();
+
+	if (type == LDObject::EUnidentified)
+		return;
+
+	// If we're selecting subfile references, the reference filename must also
+	// be uniform.
+	QString refName;
+
+	if (type == LDObject::ESubfile)
+	{
+		refName = static_cast<LDSubfile*> (selection()[0])->fileInfo()->name();
+
+		for (LDObject* obj : selection())
+			if (static_cast<LDSubfile*> (obj)->fileInfo()->name() != refName)
+				return;
+	}
+
+	getCurrentDocument()->clearSelection();
+
+	for (LDObject* obj : getCurrentDocument()->objects())
+	{
+		if (obj->type() != type)
+			continue;
+
+		if (type == LDObject::ESubfile && static_cast<LDSubfile*> (obj)->fileInfo()->name() != refName)
+			continue;
+
+		obj->select();
+	}
+
+	updateSelection();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (GridCoarse, 0)
+{
+	grid = Grid::Coarse;
+	updateGridToolBar();
+}
+
+DEFINE_ACTION (GridMedium, 0)
+{
+	grid = Grid::Medium;
+	updateGridToolBar();
+}
+
+DEFINE_ACTION (GridFine, 0)
+{
+	grid = Grid::Fine;
+	updateGridToolBar();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (ResetView, CTRL (0))
+{
+	R()->resetAngles();
+	R()->update();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (InsertFrom, 0)
+{
+	QString fname = QFileDialog::getOpenFileName();
+	int idx = getInsertionPoint();
+
+	if (!fname.length())
+		return;
+
+	QFile f (fname);
+
+	if (!f.open (QIODevice::ReadOnly))
+	{
+		critical (format ("Couldn't open %1 (%2)", fname, f.errorString()));
+		return;
+	}
+
+	LDObjectList objs = loadFileContents (&f, null);
+
+	getCurrentDocument()->clearSelection();
+
+	for (LDObject* obj : objs)
+	{
+		getCurrentDocument()->insertObj (idx, obj);
+		obj->select();
+		R()->compileObject (obj);
+
+		idx++;
+	}
+
+	refresh();
+	scrollToSelection();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (ExportTo, 0)
+{
+	if (selection().isEmpty())
+		return;
+
+	QString fname = QFileDialog::getSaveFileName();
+
+	if (fname.length() == 0)
+		return;
+
+	QFile file (fname);
+
+	if (!file.open (QIODevice::WriteOnly | QIODevice::Text))
+	{
+		critical (format ("Unable to open %1 for writing (%2)", fname, file.errorString()));
+		return;
+	}
+
+	for (LDObject* obj : selection())
+	{
+		QString contents = obj->asText();
+		QByteArray data = contents.toUtf8();
+		file.write (data, data.size());
+		file.write ("\r\n", 2);
+	}
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (InsertRaw, 0)
+{
+	int idx = getInsertionPoint();
+
+	QDialog* const dlg = new QDialog;
+	QVBoxLayout* const layout = new QVBoxLayout;
+	QTextEdit* const te_edit = new QTextEdit;
+	QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+
+	layout->addWidget (te_edit);
+	layout->addWidget (bbx_buttons);
+	dlg->setLayout (layout);
+	dlg->setWindowTitle (APPNAME ": Insert Raw");
+	dlg->connect (bbx_buttons, SIGNAL (accepted()), dlg, SLOT (accept()));
+	dlg->connect (bbx_buttons, SIGNAL (rejected()), dlg, SLOT (reject()));
+
+	if (dlg->exec() == false)
+		return;
+
+	getCurrentDocument()->clearSelection();
+
+	for (QString line : QString (te_edit->toPlainText()).split ("\n"))
+	{
+		LDObject* obj = parseLine (line);
+
+		getCurrentDocument()->insertObj (idx, obj);
+		obj->select();
+		R()->compileObject (obj);
+		idx++;
+	}
+
+	refresh();
+	scrollToSelection();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Screenshot, 0)
+{
+	setlocale (LC_ALL, "C");
+
+	int w, h;
+	uchar* imgdata = R()->getScreencap (w, h);
+	QImage img = imageFromScreencap (imgdata, w, h);
+
+	QString root = basename (getCurrentDocument()->name());
+
+	if (root.right (4) == ".dat")
+		root.chop (4);
+
+	QString defaultname = (root.length() > 0) ? format ("%1.png", root) : "";
+	QString fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname,
+				"PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)");
+
+	if (fname.length() > 0 && !img.save (fname))
+		critical (format ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno)));
+
+	delete[] imgdata;
+}
+
+// =============================================================================
+//
+extern_cfg (Bool, gl_axes);
+DEFINE_ACTION (Axes, 0)
+{
+	gl_axes = !gl_axes;
+	updateActions();
+	R()->update();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (VisibilityToggle, 0)
+{
+	for (LDObject* obj : selection())
+		obj->setHidden (!obj->isHidden());
+
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (VisibilityHide, 0)
+{
+	for (LDObject* obj : selection())
+		obj->setHidden (true);
+
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (VisibilityReveal, 0)
+{
+	for (LDObject* obj : selection())
+	obj->setHidden (false);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Wireframe, 0)
+{
+	gl_wireframe = !gl_wireframe;
+	R()->refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SetOverlay,  0)
+{
+	OverlayDialog dlg;
+
+	if (!dlg.exec())
+		return;
+
+	R()->setupOverlay ((GL::EFixedCamera) dlg.camera(), dlg.fpath(), dlg.ofsx(),
+		dlg.ofsy(), dlg.lwidth(), dlg.lheight());
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (ClearOverlay, 0)
+{
+	R()->clearOverlay();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (ModeSelect, CTRL (1))
+{
+	R()->setEditMode (ESelectMode);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (ModeDraw, CTRL (2))
+{
+	R()->setEditMode (EDrawMode);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (ModeCircle, CTRL (3))
+{
+	R()->setEditMode (ECircleMode);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (DrawAngles, 0)
+{
+	gl_drawangles = !gl_drawangles;
+	R()->refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SetDrawDepth, 0)
+{
+	if (R()->camera() == GL::EFreeCamera)
+		return;
+
+	bool ok;
+	double depth = QInputDialog::getDouble (g_win, "Set Draw Depth",
+											format ("Depth value for %1 Camera:", R()->getCameraName()),
+											R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok);
+
+	if (ok)
+		R()->setDepthValue (depth);
+}
+
+#if 0
+// This is a test to draw a dummy axle. Meant to be used as a primitive gallery,
+// but I can't figure how to generate these pictures properly. Multi-threading
+// these is an immense pain.
+DEFINE_ACTION (testpic, "Test picture", "", "", (0))
+{
+	LDDocument* file = getFile ("axle.dat");
+	setlocale (LC_ALL, "C");
+
+	if (!file)
+	{
+		critical ("couldn't load axle.dat");
+		return;
+	}
+
+	int w, h;
+
+	GLRenderer* rend = new GLRenderer;
+	rend->resize (64, 64);
+	rend->setAttribute (Qt::WA_DontShowOnScreen);
+	rend->show();
+	rend->setFile (file);
+	rend->setDrawOnly (true);
+	rend->compileAllObjects();
+	rend->initGLData();
+	rend->drawGLScene();
+
+	uchar* imgdata = rend->screencap (w, h);
+	QImage img = imageFromScreencap (imgdata, w, h);
+
+	if (img.isNull())
+	{
+		critical ("Failed to create the image!\n");
+	}
+	else
+	{
+		QLabel* label = new QLabel;
+		QDialog* dlg = new QDialog;
+		label->setPixmap (QPixmap::fromImage (img));
+		QVBoxLayout* layout = new QVBoxLayout (dlg);
+		layout->addWidget (label);
+		dlg->exec();
+	}
+
+	delete[] imgdata;
+	rend->deleteLater();
+}
+#endif
+
+// =============================================================================
+//
+DEFINE_ACTION (ScanPrimitives, 0)
+{
+	PrimitiveScanner::start();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (BFCView, SHIFT (B))
+{
+	gl_colorbfc = !gl_colorbfc;
+	updateActions();
+	R()->refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (JumpTo, CTRL (G))
+{
+	bool ok;
+	int defval = 0;
+	LDObject* obj;
+
+	if (selection().size() == 1)
+		defval = selection()[0]->lineNumber();
+
+	int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval,
+		1, getCurrentDocument()->getObjectCount(), 1, &ok);
+
+	if (!ok || (obj = getCurrentDocument()->getObject (idx - 1)) == null)
+		return;
+
+	getCurrentDocument()->clearSelection();
+	obj->select();
+	updateSelection();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SubfileSelection, 0)
+{
+	if (selection().size() == 0)
+		return;
+
+	QString			parentpath = getCurrentDocument()->fullPath();
+
+	// BFC type of the new subfile - it shall inherit the BFC type of the parent document
+	LDBFC::Statement		bfctype = LDBFC::NoCertify;
+
+	// Dirname of the new subfile
+	QString			subdirname = dirname (parentpath);
+
+	// Title of the new subfile
+	QString			subtitle;
+
+	// Comment containing the title of the parent document
+	LDComment*		titleobj = dynamic_cast<LDComment*> (getCurrentDocument()->getObject (0));
+
+	// License text for the subfile
+	QString			license = getLicenseText (ld_defaultlicense);
+
+	// LDraw code body of the new subfile (i.e. code of the selection)
+	QStringList		code;
+
+	// Full path of the subfile to be
+	QString			fullsubname;
+
+	// Where to insert the subfile reference?
+	int				refidx = selection()[0]->lineNumber();
+
+	// Determine title of subfile
+	if (titleobj != null)
+		subtitle = "~" + titleobj->text();
+	else
+		subtitle = "~subfile";
+
+	// Remove duplicate tildes
+	while (subtitle[0] == '~' && subtitle[1] == '~')
+		subtitle.remove (0, 1);
+
+	// If this the parent document isn't already in s/, we need to stuff it into
+	// a subdirectory named s/. Ensure it exists!
+	QString topdirname = basename (dirname (getCurrentDocument()->fullPath()));
+
+	if (topdirname != "s")
+	{
+		QString desiredPath = subdirname + "/s";
+		QString title = tr ("Create subfile directory?");
+		QString text = format (tr ("The directory <b>%1</b> is suggested for "
+			"subfiles. This directory does not exist, create it?"), desiredPath);
+
+		if (QDir (desiredPath).exists() || confirm (title, text))
+		{
+			subdirname = desiredPath;
+			QDir().mkpath (subdirname);
+		}
+	}
+
+	// Determine the body of the name of the subfile
+	if (!parentpath.isEmpty())
+	{
+		if (parentpath.endsWith (".dat"))
+			parentpath.chop (4);
+
+		// Remove the s?? suffix if it's there, otherwise we'll get filenames
+		// like s01s01.dat when subfiling subfiles.
+		QRegExp subfilesuffix ("s[0-9][0-9]$");
+		if (subfilesuffix.indexIn (parentpath) != -1)
+			parentpath.chop (subfilesuffix.matchedLength());
+
+		int subidx = 1;
+		QString digits;
+		QFile f;
+		QString testfname;
+
+		do
+		{
+			digits.setNum (subidx++);
+
+			// pad it with a zero
+			if (digits.length() == 1)
+				digits.prepend ("0");
+
+			fullsubname = subdirname + "/" + basename (parentpath) + "s" + digits + ".dat";
+		} while (findDocument ("s\\" + basename (fullsubname)) != null || QFile (fullsubname).exists());
+	}
+
+	// Determine the BFC winding type used in the main document - it is to
+	// be carried over to the subfile.
+	for (LDObject* obj : getCurrentDocument()->objects())
+	{
+		LDBFC* bfc = dynamic_cast<LDBFC*> (obj);
+
+		if (!bfc)
+			continue;
+
+		LDBFC::Statement a = bfc->statement();
+
+		if (a == LDBFC::CertifyCCW || a == LDBFC::CertifyCW || a == LDBFC::NoCertify)
+		{
+			bfctype = a;
+			break;
+		}
+	}
+
+	// Get the body of the document in LDraw code
+	for (LDObject* obj : selection())
+		code << obj->asText();
+
+	// Create the new subfile document
+	LDDocument* doc = new LDDocument;
+	doc->setImplicit (false);
+	doc->setFullPath (fullsubname);
+	doc->setName (LDDocument::shortenName (fullsubname));
+	doc->addObjects (
+	{
+		new LDComment (subtitle),
+		new LDComment ("Name: "),
+		new LDComment (format ("Author: %1 [%2]", ld_defaultname, ld_defaultuser)),
+		new LDComment (format ("!LDRAW_ORG Unofficial_Subpart")),
+		(license != "" ? new LDComment (license) : null),
+		new LDEmpty,
+		new LDBFC (bfctype),
+		new LDEmpty,
+	});
+
+	// Add the actual subfile code to the new document
+	for (QString line : code)
+	{
+		LDObject* obj = parseLine (line);
+		doc->addObject (obj);
+	}
+
+	// Try save it
+	if (save (doc, true))
+	{
+		// Remove the selection now
+		for (LDObject* obj : selection())
+			obj->destroy();
+
+		// Compile all objects in the new subfile
+		for (LDObject* obj : doc->objects())
+			R()->compileObject (obj);
+
+		g_loadedFiles << doc;
+
+		// Add a reference to the new subfile to where the selection was
+		LDSubfile* ref = new LDSubfile();
+		ref->setColor (maincolor);
+		ref->setFileInfo (doc);
+		ref->setPosition (g_origin);
+		ref->setTransform (g_identity);
+		getCurrentDocument()->insertObj (refidx, ref);
+		R()->compileObject (ref);
+
+		// Refresh stuff
+		updateDocumentList();
+		doFullRefresh();
+	}
+	else
+	{
+		// Failed to save.
+		delete doc;
+	}
+}
--- a/src/actions/EditActions.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,836 +0,0 @@
-/*
- *  LDForge: LDasText parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QSpinBox>
-#include <QCheckBox>
-#include <QBoxLayout>
-#include <QClipboard>
-#include "../MainWindow.h"
-#include "../Main.h"
-#include "../Document.h"
-#include "../ColorSelector.h"
-#include "../Misc.h"
-#include "../Widgets.h"
-#include "../GLRenderer.h"
-#include "../Dialogs.h"
-#include "../Colors.h"
-#include "ui_replcoords.h"
-#include "ui_editraw.h"
-#include "ui_flip.h"
-#include "ui_addhistoryline.h"
-
-cfg (Bool, edit_schemanticinline, false);
-extern_cfg (String, ld_defaultuser);
-
-// =============================================================================
-//
-static int copyToClipboard()
-{
-	LDObjectList objs = selection();
-	int num = 0;
-
-	// Clear the clipboard first.
-	qApp->clipboard()->clear();
-
-	// Now, copy the contents into the clipboard.
-	QString data;
-
-	for (LDObject* obj : objs)
-	{
-		if (data.length() > 0)
-			data += "\n";
-
-		data += obj->asText();
-		++num;
-	}
-
-	qApp->clipboard()->setText (data);
-	return num;
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Cut, CTRL (X))
-{
-	int num = copyToClipboard();
-	deleteSelection();
-	print (tr ("%1 objects cut"), num);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Copy, CTRL (C))
-{
-	int num = copyToClipboard();
-	print (tr ("%1 objects copied"), num);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Paste, CTRL (V))
-{
-	const QString clipboardText = qApp->clipboard()->text();
-	int idx = getInsertionPoint();
-	getCurrentDocument()->clearSelection();
-	int num = 0;
-
-	for (QString line : clipboardText.split ("\n"))
-	{
-		LDObject* pasted = parseLine (line);
-		getCurrentDocument()->insertObj (idx++, pasted);
-		pasted->select();
-		R()->compileObject (pasted);
-		++num;
-	}
-
-	print (tr ("%1 objects pasted"), num);
-	refresh();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Delete, KEY (Delete))
-{
-	int num = deleteSelection();
-	print (tr ("%1 objects deleted"), num);
-}
-
-// =============================================================================
-//
-static void doInline (bool deep)
-{
-	LDObjectList sel = selection();
-
-	for (LDObject* obj : sel)
-	{
-		// Get the index of the subfile so we know where to insert the
-		// inlined contents.
-		long idx = obj->lineNumber();
-
-		if (idx == -1)
-			continue;
-
-		LDObjectList objs;
-
-		if (obj->type() == LDObject::ESubfile)
-		{
-			LDSubfile::InlineFlags flags = deep ? LDSubfile::DeepCacheInline : LDSubfile::CacheInline;
-			objs = static_cast<LDSubfile*> (obj)->inlineContents (flags);
-		}
-		else
-			continue;
-
-		// Merge in the inlined objects
-		for (LDObject* inlineobj : objs)
-		{
-			QString line = inlineobj->asText();
-			inlineobj->destroy();
-			LDObject* newobj = parseLine (line);
-			getCurrentDocument()->insertObj (idx++, newobj);
-			newobj->select();
-			g_win->R()->compileObject (newobj);
-		}
-
-		// Delete the subfile now as it's been inlined.
-		obj->destroy();
-	}
-
-	g_win->refresh();
-}
-
-DEFINE_ACTION (Inline, CTRL (I))
-{
-	doInline (false);
-}
-
-DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I))
-{
-	doInline (true);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SplitQuads, 0)
-{
-	LDObjectList objs = selection();
-	int num = 0;
-
-	for (LDObject* obj : objs)
-	{
-		if (obj->type() != LDObject::EQuad)
-			continue;
-
-		// Find the index of this quad
-		long index = obj->lineNumber();
-
-		if (index == -1)
-			return;
-
-		QList<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles();
-
-		// Replace the quad with the first triangle and add the second triangle
-		// after the first one.
-		getCurrentDocument()->setObject (index, triangles[0]);
-		getCurrentDocument()->insertObj (index + 1, triangles[1]);
-
-		for (LDTriangle* t : triangles)
-			R()->compileObject (t);
-
-		// Delete this quad now, it has been split.
-		obj->destroy();
-
-		num++;
-	}
-
-	print ("%1 quadrilaterals split", num);
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (EditRaw, KEY (F9))
-{
-	if (selection().size() != 1)
-		return;
-
-	LDObject* obj = selection()[0];
-	QDialog* dlg = new QDialog;
-	Ui::EditRawUI ui;
-
-	ui.setupUi (dlg);
-	ui.code->setText (obj->asText());
-
-	if (obj->type() == LDObject::EError)
-		ui.errorDescription->setText (static_cast<LDError*> (obj)->reason());
-	else
-	{
-		ui.errorDescription->hide();
-		ui.errorIcon->hide();
-	}
-
-	if (!dlg->exec())
-		return;
-
-	LDObject* oldobj = obj;
-
-	// Reinterpret it from the text of the input field
-	obj = parseLine (ui.code->text());
-	oldobj->replace (obj);
-
-	// Refresh
-	R()->compileObject (obj);
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SetColor, KEY (C))
-{
-	if (selection().isEmpty())
-		return;
-
-	int colnum;
-	int defcol = -1;
-
-	LDObjectList objs = selection();
-
-	// If all selected objects have the same color, said color is our default
-	// value to the color selection dialog.
-	defcol = getSelectedColor();
-
-	// Show the dialog to the user now and ask for a color.
-	if (ColorSelector::selectColor (colnum, defcol, g_win))
-	{
-		for (LDObject* obj : objs)
-		{
-			if (obj->isColored() == false)
-				continue;
-
-			obj->setColor (colnum);
-			R()->compileObject (obj);
-		}
-
-		refresh();
-	}
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Borders, CTRL_SHIFT (B))
-{
-	LDObjectList objs = selection();
-	int num = 0;
-
-	for (LDObject* obj : objs)
-	{
-		const LDObject::Type type = obj->type();
-		if (type != LDObject::EQuad && type != LDObject::ETriangle)
-			continue;
-
-		int numLines;
-		LDLine* lines[4];
-
-		if (type == LDObject::EQuad)
-		{
-			numLines = 4;
-
-			LDQuad* quad = static_cast<LDQuad*> (obj);
-			lines[0] = new LDLine (quad->vertex (0), quad->vertex (1));
-			lines[1] = new LDLine (quad->vertex (1), quad->vertex (2));
-			lines[2] = new LDLine (quad->vertex (2), quad->vertex (3));
-			lines[3] = new LDLine (quad->vertex (3), quad->vertex (0));
-		}
-		else
-		{
-			numLines = 3;
-
-			LDTriangle* tri = static_cast<LDTriangle*> (obj);
-			lines[0] = new LDLine (tri->vertex (0), tri->vertex (1));
-			lines[1] = new LDLine (tri->vertex (1), tri->vertex (2));
-			lines[2] = new LDLine (tri->vertex (2), tri->vertex (0));
-		}
-
-		for (int i = 0; i < numLines; ++i)
-		{
-			long idx = obj->lineNumber() + i + 1;
-
-			lines[i]->setColor (edgecolor);
-			getCurrentDocument()->insertObj (idx, lines[i]);
-			R()->compileObject (lines[i]);
-		}
-
-		num += numLines;
-	}
-
-	print (tr ("Added %1 border lines"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (CornerVerts, 0)
-{
-	int num = 0;
-
-	for (LDObject* obj : selection())
-	{
-		if (obj->vertices() < 2)
-			continue;
-
-		int ln = obj->lineNumber();
-
-		for (int i = 0; i < obj->vertices(); ++i)
-		{
-			LDVertex* vert = new LDVertex;
-			vert->pos = obj->vertex (i);
-			vert->setColor (obj->color());
-
-			getCurrentDocument()->insertObj (++ln, vert);
-			R()->compileObject (vert);
-			++num;
-		}
-	}
-
-	print (tr ("Added %1 vertices"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-static void doMoveSelection (const bool up)
-{
-	LDObjectList objs = selection();
-	LDObject::moveObjects (objs, up);
-	g_win->buildObjList();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (MoveUp, KEY (PageUp))
-{
-	doMoveSelection (true);
-}
-
-DEFINE_ACTION (MoveDown, KEY (PageDown))
-{
-	doMoveSelection (false);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Undo, CTRL (Z))
-{
-	getCurrentDocument()->undo();
-}
-
-DEFINE_ACTION (Redo, CTRL_SHIFT (Z))
-{
-	getCurrentDocument()->redo();
-}
-
-// =============================================================================
-//
-void doMoveObjects (Vertex vect)
-{
-	// Apply the grid values
-	vect[X] *= *currentGrid().confs[Grid::X];
-	vect[Y] *= *currentGrid().confs[Grid::Y];
-	vect[Z] *= *currentGrid().confs[Grid::Z];
-
-	for (LDObject* obj : selection())
-	{
-		obj->move (vect);
-		g_win->R()->compileObject (obj);
-	}
-
-	g_win->refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (MoveXNeg, KEY (Left))
-{
-	doMoveObjects ({ -1, 0, 0});
-}
-
-DEFINE_ACTION (MoveYNeg, KEY (Home))
-{
-	doMoveObjects ({0, -1, 0});
-}
-
-DEFINE_ACTION (MoveZNeg, KEY (Down))
-{
-	doMoveObjects ({0, 0, -1});
-}
-
-DEFINE_ACTION (MoveXPos, KEY (Right))
-{
-	doMoveObjects ({1, 0, 0});
-}
-
-DEFINE_ACTION (MoveYPos, KEY (End))
-{
-	doMoveObjects ({0, 1, 0});
-}
-
-DEFINE_ACTION (MoveZPos, KEY (Up))
-{
-	doMoveObjects ({0, 0, 1});
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Invert, CTRL_SHIFT (W))
-{
-	LDObjectList sel = selection();
-
-	for (LDObject* obj : sel)
-	{
-		obj->invert();
-		R()->compileObject (obj);
-	}
-
-	refresh();
-}
-
-// =============================================================================
-//
-static void rotateVertex (Vertex& v, const Vertex& rotpoint, const Matrix& transform)
-{
-	v.move (-rotpoint);
-	v.transform (transform, g_origin);
-	v.move (rotpoint);
-}
-
-// =============================================================================
-//
-static void doRotate (const int l, const int m, const int n)
-{
-	LDObjectList sel = selection();
-	QList<Vertex*> queue;
-	const Vertex rotpoint = rotPoint (sel);
-	const double angle = (pi * *currentGrid().confs[Grid::Angle]) / 180,
-				 cosangle = cos (angle),
-				 sinangle = sin (angle);
-
-	// ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2
-	Matrix transform (
-	{
-		(l* l * (1 - cosangle)) + cosangle,
-		(m* l * (1 - cosangle)) - (n* sinangle),
-		(n* l * (1 - cosangle)) + (m* sinangle),
-
-		(l* m * (1 - cosangle)) + (n* sinangle),
-		(m* m * (1 - cosangle)) + cosangle,
-		(n* m * (1 - cosangle)) - (l* sinangle),
-
-		(l* n * (1 - cosangle)) - (m* sinangle),
-		(m* n * (1 - cosangle)) + (l* sinangle),
-		(n* n * (1 - cosangle)) + cosangle
-	});
-
-	// Apply the above matrix to everything
-	for (LDObject* obj : sel)
-	{
-		if (obj->vertices())
-		{
-			for (int i = 0; i < obj->vertices(); ++i)
-			{
-				Vertex v = obj->vertex (i);
-				rotateVertex (v, rotpoint, transform);
-				obj->setVertex (i, v);
-			}
-		}
-		elif (obj->hasMatrix())
-		{
-			LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
-
-			// Transform the position
-			Vertex v = mo->position();
-			rotateVertex (v, rotpoint, transform);
-			mo->setPosition (v);
-
-			// Transform the matrix
-			mo->setTransform (transform * mo->transform());
-		}
-		elif (obj->type() == LDObject::EVertex)
-		{
-			LDVertex* vert = static_cast<LDVertex*> (obj);
-			Vertex v = vert->pos;
-			rotateVertex (v, rotpoint, transform);
-			vert->pos = v;
-		}
-
-		g_win->R()->compileObject (obj);
-	}
-
-	g_win->refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (RotateXPos, CTRL (Right))
-{
-	doRotate (1, 0, 0);
-}
-DEFINE_ACTION (RotateYPos, CTRL (End))
-{
-	doRotate (0, 1, 0);
-}
-DEFINE_ACTION (RotateZPos, CTRL (Up))
-{
-	doRotate (0, 0, 1);
-}
-DEFINE_ACTION (RotateXNeg, CTRL (Left))
-{
-	doRotate (-1, 0, 0);
-}
-DEFINE_ACTION (RotateYNeg, CTRL (Home))
-{
-	doRotate (0, -1, 0);
-}
-DEFINE_ACTION (RotateZNeg, CTRL (Down))
-{
-	doRotate (0, 0, -1);
-}
-
-DEFINE_ACTION (RotationPoint, (0))
-{
-	configRotationPoint();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (RoundCoordinates, 0)
-{
-	setlocale (LC_ALL, "C");
-	int num = 0;
-
-	for (LDObject* obj : selection())
-	{
-		LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
-
-		if (mo != null)
-		{
-			Vertex v = mo->position();
-			Matrix t = mo->transform();
-
-			for_axes (ax)
-				roundToDecimals (v[ax], 3);
-
-			// Let matrix values be rounded to 4 decimals,
-			// they need that extra precision
-			for (int i = 0; i < 9; ++i)
-				roundToDecimals (t[i], 4);
-
-			mo->setPosition (v);
-			mo->setTransform (t);
-			num += 10;
-		}
-		else
-		{
-			for (int i = 0; i < obj->vertices(); ++i)
-			{
-				Vertex v = obj->vertex (i);
-
-				for_axes (ax)
-					roundToDecimals (v[ax], 3);
-
-				obj->setVertex (i, v);
-				R()->compileObject (obj);
-				num += 3;
-			}
-		}
-	}
-
-	print (tr ("Rounded %1 values"), num);
-	refreshObjectList();
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Uncolorize, 0)
-{
-	int num = 0;
-
-	for (LDObject* obj : selection())
-	{
-		if (obj->isColored() == false)
-			continue;
-
-		int col = maincolor;
-
-		if (obj->type() == LDObject::ELine || obj->type() == LDObject::ECondLine)
-			col = edgecolor;
-
-		obj->setColor (col);
-		R()->compileObject (obj);
-		num++;
-	}
-
-	print (tr ("%1 objects uncolored"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (ReplaceCoords, CTRL (R))
-{
-	QDialog* dlg = new QDialog (g_win);
-	Ui::ReplaceCoordsUI ui;
-	ui.setupUi (dlg);
-
-	if (!dlg->exec())
-		return;
-
-	const double search = ui.search->value(),
-		replacement = ui.replacement->value();
-	const bool any = ui.any->isChecked(),
-		rel = ui.relative->isChecked();
-
-	QList<Axis> sel;
-	int num = 0;
-
-	if (ui.x->isChecked()) sel << X;
-	if (ui.y->isChecked()) sel << Y;
-	if (ui.z->isChecked()) sel << Z;
-
-	for (LDObject* obj : selection())
-	{
-		for (int i = 0; i < obj->vertices(); ++i)
-		{
-			Vertex v = obj->vertex (i);
-
-			for (Axis ax : sel)
-			{
-				double& coord = v[ax];
-
-				if (any || coord == search)
-				{
-					if (!rel)
-						coord = 0;
-
-					coord += replacement;
-					num++;
-				}
-			}
-
-			obj->setVertex (i, v);
-			R()->compileObject (obj);
-		}
-	}
-
-	print (tr ("Altered %1 values"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Flip, CTRL_SHIFT (F))
-{
-	QDialog* dlg = new QDialog;
-	Ui::FlipUI ui;
-	ui.setupUi (dlg);
-
-	if (!dlg->exec())
-		return;
-
-	QList<Axis> sel;
-
-	if (ui.x->isChecked()) sel << X;
-	if (ui.y->isChecked()) sel << Y;
-	if (ui.z->isChecked()) sel << Z;
-
-	for (LDObject* obj : selection())
-	{
-		for (int i = 0; i < obj->vertices(); ++i)
-		{
-			Vertex v = obj->vertex (i);
-
-			for (Axis ax : sel)
-				v[ax] *= -1;
-
-			obj->setVertex (i, v);
-			R()->compileObject (obj);
-		}
-	}
-
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Demote, 0)
-{
-	LDObjectList sel = selection();
-	int num = 0;
-
-	for (LDObject* obj : sel)
-	{
-		if (obj->type() != LDObject::ECondLine)
-			continue;
-
-		LDLine* repl = static_cast<LDCondLine*> (obj)->demote();
-		R()->compileObject (repl);
-		++num;
-	}
-
-	print (tr ("Demoted %1 conditional lines"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-static bool isColorUsed (int colnum)
-{
-	for (LDObject* obj : getCurrentDocument()->objects())
-		if (obj->isColored() && obj->color() == colnum)
-			return true;
-
-	return false;
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Autocolor, 0)
-{
-	int colnum = 0;
-
-	while (colnum < MAX_COLORS && (getColor (colnum) == null || isColorUsed (colnum)))
-		colnum++;
-
-	if (colnum >= MAX_COLORS)
-	{
-		print (tr ("Cannot auto-color: all colors are in use!"));
-		return;
-	}
-
-	for (LDObject* obj : selection())
-	{
-		if (obj->isColored() == false)
-			continue;
-
-		obj->setColor (colnum);
-		R()->compileObject (obj);
-	}
-
-	print (tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name);
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (AddHistoryLine, 0)
-{
-	LDObject* obj;
-	bool ishistory = false,
-		 prevIsHistory = false;
-
-	QDialog* dlg = new QDialog;
-	Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
-	ui->setupUi (dlg);
-	ui->m_username->setText (ld_defaultuser);
-	ui->m_date->setDate (QDate::currentDate());
-	ui->m_comment->setFocus();
-
-	if (!dlg->exec())
-		return;
-
-	// Create the comment object based on input
-	QString commentText = format ("!HISTORY %1 [%2] %3",
-		ui->m_date->date().toString ("yyyy-MM-dd"),
-		ui->m_username->text(),
-		ui->m_comment->text());
-
-	LDComment* comm = new LDComment (commentText);
-
-	// Find a spot to place the new comment
-	for (
-		obj = getCurrentDocument()->getObject (0);
-		obj && obj->next() && !obj->next()->isScemantic();
-		obj = obj->next()
-	)
-	{
-		LDComment* comm = dynamic_cast<LDComment*> (obj);
-
-		if (comm != null && comm->text().startsWith ("!HISTORY "))
-			ishistory = true;
-
-		if (prevIsHistory && !ishistory)
-		{
-			// Last line was history, this isn't, thus insert the new history
-			// line here.
-			break;
-		}
-
-		prevIsHistory = ishistory;
-	}
-
-	int idx = obj ? obj->lineNumber() : 0;
-	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())
-		getCurrentDocument()->insertObj (idx, new LDEmpty);
-
-	buildObjList();
-	delete ui;
-}
--- a/src/actions/MainActions.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,873 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QFileDialog>
-#include <QMessageBox>
-#include <QTextEdit>
-#include <QBoxLayout>
-#include <QDialogButtonBox>
-#include <QPushButton>
-#include <QInputDialog>
-
-#include "../MainWindow.h"
-#include "../Document.h"
-#include "../EditHistory.h"
-#include "../ConfigurationDialog.h"
-#include "../AddObjectDialog.h"
-#include "../Misc.h"
-#include "../GLRenderer.h"
-#include "../Dialogs.h"
-#include "../Primitives.h"
-#include "../Widgets.h"
-#include "../Colors.h"
-#include "ui_newpart.h"
-
-extern_cfg (Bool,		gl_wireframe);
-extern_cfg (Bool,		gl_colorbfc);
-extern_cfg (String,	ld_defaultname);
-extern_cfg (String,	ld_defaultuser);
-extern_cfg (Int,		ld_defaultlicense);
-extern_cfg (Bool,		gl_drawangles);
-
-// =============================================================================
-//
-DEFINE_ACTION (New, CTRL_SHIFT (N))
-{
-	QDialog* dlg = new QDialog (g_win);
-	Ui::NewPartUI ui;
-	ui.setupUi (dlg);
-
-	QString authortext = ld_defaultname;
-
-	if (!ld_defaultuser.isEmpty())
-		authortext.append (format (" [%1]", ld_defaultuser));
-
-	ui.le_author->setText (authortext);
-
-	switch (ld_defaultlicense)
-	{
-		case 0:
-			ui.rb_license_ca->setChecked (true);
-			break;
-
-		case 1:
-			ui.rb_license_nonca->setChecked (true);
-			break;
-
-		case 2:
-			ui.rb_license_none->setChecked (true);
-			break;
-
-		default:
-			QMessageBox::warning (null, "Warning",
-				format ("Unknown ld_defaultlicense value %1!", ld_defaultlicense));
-			break;
-	}
-
-	if (dlg->exec() == false)
-		return;
-
-	newFile();
-
-	const LDBFC::Statement BFCType =
-		ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW :
-		ui.rb_bfc_cw->isChecked()  ? LDBFC::CertifyCW : LDBFC::NoCertify;
-
-	const QString license =
-		ui.rb_license_ca->isChecked()    ? g_CALicense :
-		ui.rb_license_nonca->isChecked() ? g_nonCALicense : "";
-
-	getCurrentDocument()->addObjects (
-	{
-		new LDComment (ui.le_title->text()),
-		new LDComment ("Name: <untitled>.dat"),
-		new LDComment (format ("Author: %1", ui.le_author->text())),
-		new LDComment (format ("!LDRAW_ORG Unofficial_Part")),
-		(license != "" ? new LDComment (license) : null),
-		new LDEmpty,
-		new LDBFC (BFCType),
-		new LDEmpty,
-	});
-
-	doFullRefresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (NewFile, CTRL (N))
-{
-	newFile();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Open, CTRL (O))
-{
-	QString name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)");
-
-	if (name.length() == 0)
-		return;
-
-	openMainFile (name);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Save, CTRL (S))
-{
-	save (getCurrentDocument(), false);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SaveAs, CTRL_SHIFT (S))
-{
-	save (getCurrentDocument(), true);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SaveAll, CTRL (L))
-{
-	for (LDDocument* file : g_loadedFiles)
-	{
-		if (file->isImplicit())
-			continue;
-
-		save (file, false);
-	}
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Close, CTRL (W))
-{
-	if (!getCurrentDocument()->isSafeToClose())
-		return;
-
-	delete getCurrentDocument();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (CloseAll, 0)
-{
-	if (!safeToCloseAll())
-		return;
-
-	closeAll();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Settings, 0)
-{
-	(new ConfigDialog)->exec();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SetLDrawPath, 0)
-{
-	(new LDrawPathDialog (true))->exec();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Exit, CTRL (Q))
-{
-	exit (0);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (NewSubfile, 0)
-{
-	AddObjectDialog::staticDialog (LDObject::ESubfile, null);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (NewLine, 0)
-{
-	AddObjectDialog::staticDialog (LDObject::ELine, null);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (NewTriangle, 0)
-{
-	AddObjectDialog::staticDialog (LDObject::ETriangle, null);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (NewQuad, 0)
-{
-	AddObjectDialog::staticDialog (LDObject::EQuad, null);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (NewCLine, 0)
-{
-	AddObjectDialog::staticDialog (LDObject::ECondLine, null);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (NewComment, 0)
-{
-	AddObjectDialog::staticDialog (LDObject::EComment, null);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (NewBFC, 0)
-{
-	AddObjectDialog::staticDialog (LDObject::EBFC, null);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (NewVertex, 0)
-{
-	AddObjectDialog::staticDialog (LDObject::EVertex, null);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Edit, 0)
-{
-	if (selection().size() != 1)
-		return;
-
-	LDObject* obj = selection() [0];
-	AddObjectDialog::staticDialog (obj->type(), obj);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Help, KEY (F1))
-{
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (About, 0)
-{
-	AboutDialog().exec();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (AboutQt, 0)
-{
-	QMessageBox::aboutQt (g_win);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SelectAll, CTRL (A))
-{
-	for (LDObject* obj : getCurrentDocument()->objects())
-		obj->select();
-
-	updateSelection();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A))
-{
-	int colnum = getSelectedColor();
-
-	if (colnum == -1)
-		return; // no consensus on color
-
-	getCurrentDocument()->clearSelection();
-
-	for (LDObject* obj : getCurrentDocument()->objects())
-		if (obj->color() == colnum)
-			obj->select();
-
-	updateSelection();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SelectByType, 0)
-{
-	if (selection().isEmpty())
-		return;
-
-	LDObject::Type type = getUniformSelectedType();
-
-	if (type == LDObject::EUnidentified)
-		return;
-
-	// If we're selecting subfile references, the reference filename must also
-	// be uniform.
-	QString refName;
-
-	if (type == LDObject::ESubfile)
-	{
-		refName = static_cast<LDSubfile*> (selection()[0])->fileInfo()->name();
-
-		for (LDObject* obj : selection())
-			if (static_cast<LDSubfile*> (obj)->fileInfo()->name() != refName)
-				return;
-	}
-
-	getCurrentDocument()->clearSelection();
-
-	for (LDObject* obj : getCurrentDocument()->objects())
-	{
-		if (obj->type() != type)
-			continue;
-
-		if (type == LDObject::ESubfile && static_cast<LDSubfile*> (obj)->fileInfo()->name() != refName)
-			continue;
-
-		obj->select();
-	}
-
-	updateSelection();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (GridCoarse, 0)
-{
-	grid = Grid::Coarse;
-	updateGridToolBar();
-}
-
-DEFINE_ACTION (GridMedium, 0)
-{
-	grid = Grid::Medium;
-	updateGridToolBar();
-}
-
-DEFINE_ACTION (GridFine, 0)
-{
-	grid = Grid::Fine;
-	updateGridToolBar();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (ResetView, CTRL (0))
-{
-	R()->resetAngles();
-	R()->update();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (InsertFrom, 0)
-{
-	QString fname = QFileDialog::getOpenFileName();
-	int idx = getInsertionPoint();
-
-	if (!fname.length())
-		return;
-
-	QFile f (fname);
-
-	if (!f.open (QIODevice::ReadOnly))
-	{
-		critical (format ("Couldn't open %1 (%2)", fname, f.errorString()));
-		return;
-	}
-
-	LDObjectList objs = loadFileContents (&f, null);
-
-	getCurrentDocument()->clearSelection();
-
-	for (LDObject* obj : objs)
-	{
-		getCurrentDocument()->insertObj (idx, obj);
-		obj->select();
-		R()->compileObject (obj);
-
-		idx++;
-	}
-
-	refresh();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (ExportTo, 0)
-{
-	if (selection().isEmpty())
-		return;
-
-	QString fname = QFileDialog::getSaveFileName();
-
-	if (fname.length() == 0)
-		return;
-
-	QFile file (fname);
-
-	if (!file.open (QIODevice::WriteOnly | QIODevice::Text))
-	{
-		critical (format ("Unable to open %1 for writing (%2)", fname, file.errorString()));
-		return;
-	}
-
-	for (LDObject* obj : selection())
-	{
-		QString contents = obj->asText();
-		QByteArray data = contents.toUtf8();
-		file.write (data, data.size());
-		file.write ("\r\n", 2);
-	}
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (InsertRaw, 0)
-{
-	int idx = getInsertionPoint();
-
-	QDialog* const dlg = new QDialog;
-	QVBoxLayout* const layout = new QVBoxLayout;
-	QTextEdit* const te_edit = new QTextEdit;
-	QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-
-	layout->addWidget (te_edit);
-	layout->addWidget (bbx_buttons);
-	dlg->setLayout (layout);
-	dlg->setWindowTitle (APPNAME ": Insert Raw");
-	dlg->connect (bbx_buttons, SIGNAL (accepted()), dlg, SLOT (accept()));
-	dlg->connect (bbx_buttons, SIGNAL (rejected()), dlg, SLOT (reject()));
-
-	if (dlg->exec() == false)
-		return;
-
-	getCurrentDocument()->clearSelection();
-
-	for (QString line : QString (te_edit->toPlainText()).split ("\n"))
-	{
-		LDObject* obj = parseLine (line);
-
-		getCurrentDocument()->insertObj (idx, obj);
-		obj->select();
-		R()->compileObject (obj);
-		idx++;
-	}
-
-	refresh();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Screenshot, 0)
-{
-	setlocale (LC_ALL, "C");
-
-	int w, h;
-	uchar* imgdata = R()->getScreencap (w, h);
-	QImage img = imageFromScreencap (imgdata, w, h);
-
-	QString root = basename (getCurrentDocument()->name());
-
-	if (root.right (4) == ".dat")
-		root.chop (4);
-
-	QString defaultname = (root.length() > 0) ? format ("%1.png", root) : "";
-	QString fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname,
-				"PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)");
-
-	if (fname.length() > 0 && !img.save (fname))
-		critical (format ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno)));
-
-	delete[] imgdata;
-}
-
-// =============================================================================
-//
-extern_cfg (Bool, gl_axes);
-DEFINE_ACTION (Axes, 0)
-{
-	gl_axes = !gl_axes;
-	updateActions();
-	R()->update();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (VisibilityToggle, 0)
-{
-	for (LDObject* obj : selection())
-		obj->setHidden (!obj->isHidden());
-
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (VisibilityHide, 0)
-{
-	for (LDObject* obj : selection())
-		obj->setHidden (true);
-
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (VisibilityReveal, 0)
-{
-	for (LDObject* obj : selection())
-	obj->setHidden (false);
-	refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (Wireframe, 0)
-{
-	gl_wireframe = !gl_wireframe;
-	R()->refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SetOverlay,  0)
-{
-	OverlayDialog dlg;
-
-	if (!dlg.exec())
-		return;
-
-	R()->setupOverlay ((GL::EFixedCamera) dlg.camera(), dlg.fpath(), dlg.ofsx(),
-		dlg.ofsy(), dlg.lwidth(), dlg.lheight());
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (ClearOverlay, 0)
-{
-	R()->clearOverlay();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (ModeSelect, CTRL (1))
-{
-	R()->setEditMode (ESelectMode);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (ModeDraw, CTRL (2))
-{
-	R()->setEditMode (EDrawMode);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (ModeCircle, CTRL (3))
-{
-	R()->setEditMode (ECircleMode);
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (DrawAngles, 0)
-{
-	gl_drawangles = !gl_drawangles;
-	R()->refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SetDrawDepth, 0)
-{
-	if (R()->camera() == GL::EFreeCamera)
-		return;
-
-	bool ok;
-	double depth = QInputDialog::getDouble (g_win, "Set Draw Depth",
-											format ("Depth value for %1 Camera:", R()->getCameraName()),
-											R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok);
-
-	if (ok)
-		R()->setDepthValue (depth);
-}
-
-#if 0
-// This is a test to draw a dummy axle. Meant to be used as a primitive gallery,
-// but I can't figure how to generate these pictures properly. Multi-threading
-// these is an immense pain.
-DEFINE_ACTION (testpic, "Test picture", "", "", (0))
-{
-	LDDocument* file = getFile ("axle.dat");
-	setlocale (LC_ALL, "C");
-
-	if (!file)
-	{
-		critical ("couldn't load axle.dat");
-		return;
-	}
-
-	int w, h;
-
-	GLRenderer* rend = new GLRenderer;
-	rend->resize (64, 64);
-	rend->setAttribute (Qt::WA_DontShowOnScreen);
-	rend->show();
-	rend->setFile (file);
-	rend->setDrawOnly (true);
-	rend->compileAllObjects();
-	rend->initGLData();
-	rend->drawGLScene();
-
-	uchar* imgdata = rend->screencap (w, h);
-	QImage img = imageFromScreencap (imgdata, w, h);
-
-	if (img.isNull())
-	{
-		critical ("Failed to create the image!\n");
-	}
-	else
-	{
-		QLabel* label = new QLabel;
-		QDialog* dlg = new QDialog;
-		label->setPixmap (QPixmap::fromImage (img));
-		QVBoxLayout* layout = new QVBoxLayout (dlg);
-		layout->addWidget (label);
-		dlg->exec();
-	}
-
-	delete[] imgdata;
-	rend->deleteLater();
-}
-#endif
-
-// =============================================================================
-//
-DEFINE_ACTION (ScanPrimitives, 0)
-{
-	PrimitiveScanner::start();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (BFCView, SHIFT (B))
-{
-	gl_colorbfc = !gl_colorbfc;
-	updateActions();
-	R()->refresh();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (JumpTo, CTRL (G))
-{
-	bool ok;
-	int defval = 0;
-	LDObject* obj;
-
-	if (selection().size() == 1)
-		defval = selection()[0]->lineNumber();
-
-	int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval,
-		1, getCurrentDocument()->getObjectCount(), 1, &ok);
-
-	if (!ok || (obj = getCurrentDocument()->getObject (idx - 1)) == null)
-		return;
-
-	getCurrentDocument()->clearSelection();
-	obj->select();
-	updateSelection();
-}
-
-// =============================================================================
-//
-DEFINE_ACTION (SubfileSelection, 0)
-{
-	if (selection().size() == 0)
-		return;
-
-	QString			parentpath = getCurrentDocument()->fullPath();
-
-	// BFC type of the new subfile - it shall inherit the BFC type of the parent document
-	LDBFC::Statement		bfctype = LDBFC::NoCertify;
-
-	// Dirname of the new subfile
-	QString			subdirname = dirname (parentpath);
-
-	// Title of the new subfile
-	QString			subtitle;
-
-	// Comment containing the title of the parent document
-	LDComment*		titleobj = dynamic_cast<LDComment*> (getCurrentDocument()->getObject (0));
-
-	// License text for the subfile
-	QString			license = getLicenseText (ld_defaultlicense);
-
-	// LDraw code body of the new subfile (i.e. code of the selection)
-	QStringList		code;
-
-	// Full path of the subfile to be
-	QString			fullsubname;
-
-	// Where to insert the subfile reference?
-	int				refidx = selection()[0]->lineNumber();
-
-	// Determine title of subfile
-	if (titleobj != null)
-		subtitle = "~" + titleobj->text();
-	else
-		subtitle = "~subfile";
-
-	// Remove duplicate tildes
-	while (subtitle[0] == '~' && subtitle[1] == '~')
-		subtitle.remove (0, 1);
-
-	// If this the parent document isn't already in s/, we need to stuff it into
-	// a subdirectory named s/. Ensure it exists!
-	QString topdirname = basename (dirname (getCurrentDocument()->fullPath()));
-
-	if (topdirname != "s")
-	{
-		QString desiredPath = subdirname + "/s";
-		QString title = tr ("Create subfile directory?");
-		QString text = format (tr ("The directory <b>%1</b> is suggested for "
-			"subfiles. This directory does not exist, create it?"), desiredPath);
-
-		if (QDir (desiredPath).exists() || confirm (title, text))
-		{
-			subdirname = desiredPath;
-			QDir().mkpath (subdirname);
-		}
-	}
-
-	// Determine the body of the name of the subfile
-	if (!parentpath.isEmpty())
-	{
-		if (parentpath.endsWith (".dat"))
-			parentpath.chop (4);
-
-		// Remove the s?? suffix if it's there, otherwise we'll get filenames
-		// like s01s01.dat when subfiling subfiles.
-		QRegExp subfilesuffix ("s[0-9][0-9]$");
-		if (subfilesuffix.indexIn (parentpath) != -1)
-			parentpath.chop (subfilesuffix.matchedLength());
-
-		int subidx = 1;
-		QString digits;
-		QFile f;
-		QString testfname;
-
-		do
-		{
-			digits.setNum (subidx++);
-
-			// pad it with a zero
-			if (digits.length() == 1)
-				digits.prepend ("0");
-
-			fullsubname = subdirname + "/" + basename (parentpath) + "s" + digits + ".dat";
-		} while (findDocument ("s\\" + basename (fullsubname)) != null || QFile (fullsubname).exists());
-	}
-
-	// Determine the BFC winding type used in the main document - it is to
-	// be carried over to the subfile.
-	for (LDObject* obj : getCurrentDocument()->objects())
-	{
-		LDBFC* bfc = dynamic_cast<LDBFC*> (obj);
-
-		if (!bfc)
-			continue;
-
-		LDBFC::Statement a = bfc->statement();
-
-		if (a == LDBFC::CertifyCCW || a == LDBFC::CertifyCW || a == LDBFC::NoCertify)
-		{
-			bfctype = a;
-			break;
-		}
-	}
-
-	// Get the body of the document in LDraw code
-	for (LDObject* obj : selection())
-		code << obj->asText();
-
-	// Create the new subfile document
-	LDDocument* doc = new LDDocument;
-	doc->setImplicit (false);
-	doc->setFullPath (fullsubname);
-	doc->setName (LDDocument::shortenName (fullsubname));
-	doc->addObjects (
-	{
-		new LDComment (subtitle),
-		new LDComment ("Name: "),
-		new LDComment (format ("Author: %1 [%2]", ld_defaultname, ld_defaultuser)),
-		new LDComment (format ("!LDRAW_ORG Unofficial_Subpart")),
-		(license != "" ? new LDComment (license) : null),
-		new LDEmpty,
-		new LDBFC (bfctype),
-		new LDEmpty,
-	});
-
-	// Add the actual subfile code to the new document
-	for (QString line : code)
-	{
-		LDObject* obj = parseLine (line);
-		doc->addObject (obj);
-	}
-
-	// Try save it
-	if (save (doc, true))
-	{
-		// Remove the selection now
-		for (LDObject* obj : selection())
-			obj->destroy();
-
-		// Compile all objects in the new subfile
-		for (LDObject* obj : doc->objects())
-			R()->compileObject (obj);
-
-		g_loadedFiles << doc;
-
-		// Add a reference to the new subfile to where the selection was
-		LDSubfile* ref = new LDSubfile();
-		ref->setColor (maincolor);
-		ref->setFileInfo (doc);
-		ref->setPosition (g_origin);
-		ref->setTransform (g_identity);
-		getCurrentDocument()->insertObj (refidx, ref);
-		R()->compileObject (ref);
-
-		// Refresh stuff
-		updateDocumentList();
-		doFullRefresh();
-	}
-	else
-	{
-		// Failed to save.
-		delete doc;
-	}
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/actionsEdit.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,836 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QSpinBox>
+#include <QCheckBox>
+#include <QBoxLayout>
+#include <QClipboard>
+#include "mainWindow.h"
+#include "main.h"
+#include "ldDocument.h"
+#include "colorSelector.h"
+#include "miscallenous.h"
+#include "radioGroup.h"
+#include "glRenderer.h"
+#include "dialogs.h"
+#include "colors.h"
+#include "ui_replcoords.h"
+#include "ui_editraw.h"
+#include "ui_flip.h"
+#include "ui_addhistoryline.h"
+
+cfg (Bool, edit_schemanticinline, false);
+extern_cfg (String, ld_defaultuser);
+
+// =============================================================================
+//
+static int copyToClipboard()
+{
+	LDObjectList objs = selection();
+	int num = 0;
+
+	// Clear the clipboard first.
+	qApp->clipboard()->clear();
+
+	// Now, copy the contents into the clipboard.
+	QString data;
+
+	for (LDObject* obj : objs)
+	{
+		if (data.length() > 0)
+			data += "\n";
+
+		data += obj->asText();
+		++num;
+	}
+
+	qApp->clipboard()->setText (data);
+	return num;
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Cut, CTRL (X))
+{
+	int num = copyToClipboard();
+	deleteSelection();
+	print (tr ("%1 objects cut"), num);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Copy, CTRL (C))
+{
+	int num = copyToClipboard();
+	print (tr ("%1 objects copied"), num);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Paste, CTRL (V))
+{
+	const QString clipboardText = qApp->clipboard()->text();
+	int idx = getInsertionPoint();
+	getCurrentDocument()->clearSelection();
+	int num = 0;
+
+	for (QString line : clipboardText.split ("\n"))
+	{
+		LDObject* pasted = parseLine (line);
+		getCurrentDocument()->insertObj (idx++, pasted);
+		pasted->select();
+		R()->compileObject (pasted);
+		++num;
+	}
+
+	print (tr ("%1 objects pasted"), num);
+	refresh();
+	scrollToSelection();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Delete, KEY (Delete))
+{
+	int num = deleteSelection();
+	print (tr ("%1 objects deleted"), num);
+}
+
+// =============================================================================
+//
+static void doInline (bool deep)
+{
+	LDObjectList sel = selection();
+
+	for (LDObject* obj : sel)
+	{
+		// Get the index of the subfile so we know where to insert the
+		// inlined contents.
+		long idx = obj->lineNumber();
+
+		if (idx == -1)
+			continue;
+
+		LDObjectList objs;
+
+		if (obj->type() == LDObject::ESubfile)
+		{
+			LDSubfile::InlineFlags flags = deep ? LDSubfile::DeepCacheInline : LDSubfile::CacheInline;
+			objs = static_cast<LDSubfile*> (obj)->inlineContents (flags);
+		}
+		else
+			continue;
+
+		// Merge in the inlined objects
+		for (LDObject* inlineobj : objs)
+		{
+			QString line = inlineobj->asText();
+			inlineobj->destroy();
+			LDObject* newobj = parseLine (line);
+			getCurrentDocument()->insertObj (idx++, newobj);
+			newobj->select();
+			g_win->R()->compileObject (newobj);
+		}
+
+		// Delete the subfile now as it's been inlined.
+		obj->destroy();
+	}
+
+	g_win->refresh();
+}
+
+DEFINE_ACTION (Inline, CTRL (I))
+{
+	doInline (false);
+}
+
+DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I))
+{
+	doInline (true);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SplitQuads, 0)
+{
+	LDObjectList objs = selection();
+	int num = 0;
+
+	for (LDObject* obj : objs)
+	{
+		if (obj->type() != LDObject::EQuad)
+			continue;
+
+		// Find the index of this quad
+		long index = obj->lineNumber();
+
+		if (index == -1)
+			return;
+
+		QList<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles();
+
+		// Replace the quad with the first triangle and add the second triangle
+		// after the first one.
+		getCurrentDocument()->setObject (index, triangles[0]);
+		getCurrentDocument()->insertObj (index + 1, triangles[1]);
+
+		for (LDTriangle* t : triangles)
+			R()->compileObject (t);
+
+		// Delete this quad now, it has been split.
+		obj->destroy();
+
+		num++;
+	}
+
+	print ("%1 quadrilaterals split", num);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (EditRaw, KEY (F9))
+{
+	if (selection().size() != 1)
+		return;
+
+	LDObject* obj = selection()[0];
+	QDialog* dlg = new QDialog;
+	Ui::EditRawUI ui;
+
+	ui.setupUi (dlg);
+	ui.code->setText (obj->asText());
+
+	if (obj->type() == LDObject::EError)
+		ui.errorDescription->setText (static_cast<LDError*> (obj)->reason());
+	else
+	{
+		ui.errorDescription->hide();
+		ui.errorIcon->hide();
+	}
+
+	if (!dlg->exec())
+		return;
+
+	LDObject* oldobj = obj;
+
+	// Reinterpret it from the text of the input field
+	obj = parseLine (ui.code->text());
+	oldobj->replace (obj);
+
+	// Refresh
+	R()->compileObject (obj);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SetColor, KEY (C))
+{
+	if (selection().isEmpty())
+		return;
+
+	int colnum;
+	int defcol = -1;
+
+	LDObjectList objs = selection();
+
+	// If all selected objects have the same color, said color is our default
+	// value to the color selection dialog.
+	defcol = getSelectedColor();
+
+	// Show the dialog to the user now and ask for a color.
+	if (ColorSelector::selectColor (colnum, defcol, g_win))
+	{
+		for (LDObject* obj : objs)
+		{
+			if (obj->isColored() == false)
+				continue;
+
+			obj->setColor (colnum);
+			R()->compileObject (obj);
+		}
+
+		refresh();
+	}
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Borders, CTRL_SHIFT (B))
+{
+	LDObjectList objs = selection();
+	int num = 0;
+
+	for (LDObject* obj : objs)
+	{
+		const LDObject::Type type = obj->type();
+		if (type != LDObject::EQuad && type != LDObject::ETriangle)
+			continue;
+
+		int numLines;
+		LDLine* lines[4];
+
+		if (type == LDObject::EQuad)
+		{
+			numLines = 4;
+
+			LDQuad* quad = static_cast<LDQuad*> (obj);
+			lines[0] = new LDLine (quad->vertex (0), quad->vertex (1));
+			lines[1] = new LDLine (quad->vertex (1), quad->vertex (2));
+			lines[2] = new LDLine (quad->vertex (2), quad->vertex (3));
+			lines[3] = new LDLine (quad->vertex (3), quad->vertex (0));
+		}
+		else
+		{
+			numLines = 3;
+
+			LDTriangle* tri = static_cast<LDTriangle*> (obj);
+			lines[0] = new LDLine (tri->vertex (0), tri->vertex (1));
+			lines[1] = new LDLine (tri->vertex (1), tri->vertex (2));
+			lines[2] = new LDLine (tri->vertex (2), tri->vertex (0));
+		}
+
+		for (int i = 0; i < numLines; ++i)
+		{
+			long idx = obj->lineNumber() + i + 1;
+
+			lines[i]->setColor (edgecolor);
+			getCurrentDocument()->insertObj (idx, lines[i]);
+			R()->compileObject (lines[i]);
+		}
+
+		num += numLines;
+	}
+
+	print (tr ("Added %1 border lines"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (CornerVerts, 0)
+{
+	int num = 0;
+
+	for (LDObject* obj : selection())
+	{
+		if (obj->vertices() < 2)
+			continue;
+
+		int ln = obj->lineNumber();
+
+		for (int i = 0; i < obj->vertices(); ++i)
+		{
+			LDVertex* vert = new LDVertex;
+			vert->pos = obj->vertex (i);
+			vert->setColor (obj->color());
+
+			getCurrentDocument()->insertObj (++ln, vert);
+			R()->compileObject (vert);
+			++num;
+		}
+	}
+
+	print (tr ("Added %1 vertices"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+static void doMoveSelection (const bool up)
+{
+	LDObjectList objs = selection();
+	LDObject::moveObjects (objs, up);
+	g_win->buildObjList();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (MoveUp, KEY (PageUp))
+{
+	doMoveSelection (true);
+}
+
+DEFINE_ACTION (MoveDown, KEY (PageDown))
+{
+	doMoveSelection (false);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Undo, CTRL (Z))
+{
+	getCurrentDocument()->undo();
+}
+
+DEFINE_ACTION (Redo, CTRL_SHIFT (Z))
+{
+	getCurrentDocument()->redo();
+}
+
+// =============================================================================
+//
+void doMoveObjects (Vertex vect)
+{
+	// Apply the grid values
+	vect[X] *= *currentGrid().confs[Grid::X];
+	vect[Y] *= *currentGrid().confs[Grid::Y];
+	vect[Z] *= *currentGrid().confs[Grid::Z];
+
+	for (LDObject* obj : selection())
+	{
+		obj->move (vect);
+		g_win->R()->compileObject (obj);
+	}
+
+	g_win->refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (MoveXNeg, KEY (Left))
+{
+	doMoveObjects ({ -1, 0, 0});
+}
+
+DEFINE_ACTION (MoveYNeg, KEY (Home))
+{
+	doMoveObjects ({0, -1, 0});
+}
+
+DEFINE_ACTION (MoveZNeg, KEY (Down))
+{
+	doMoveObjects ({0, 0, -1});
+}
+
+DEFINE_ACTION (MoveXPos, KEY (Right))
+{
+	doMoveObjects ({1, 0, 0});
+}
+
+DEFINE_ACTION (MoveYPos, KEY (End))
+{
+	doMoveObjects ({0, 1, 0});
+}
+
+DEFINE_ACTION (MoveZPos, KEY (Up))
+{
+	doMoveObjects ({0, 0, 1});
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Invert, CTRL_SHIFT (W))
+{
+	LDObjectList sel = selection();
+
+	for (LDObject* obj : sel)
+	{
+		obj->invert();
+		R()->compileObject (obj);
+	}
+
+	refresh();
+}
+
+// =============================================================================
+//
+static void rotateVertex (Vertex& v, const Vertex& rotpoint, const Matrix& transform)
+{
+	v.move (-rotpoint);
+	v.transform (transform, g_origin);
+	v.move (rotpoint);
+}
+
+// =============================================================================
+//
+static void doRotate (const int l, const int m, const int n)
+{
+	LDObjectList sel = selection();
+	QList<Vertex*> queue;
+	const Vertex rotpoint = rotPoint (sel);
+	const double angle = (pi * *currentGrid().confs[Grid::Angle]) / 180,
+				 cosangle = cos (angle),
+				 sinangle = sin (angle);
+
+	// ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2
+	Matrix transform (
+	{
+		(l* l * (1 - cosangle)) + cosangle,
+		(m* l * (1 - cosangle)) - (n* sinangle),
+		(n* l * (1 - cosangle)) + (m* sinangle),
+
+		(l* m * (1 - cosangle)) + (n* sinangle),
+		(m* m * (1 - cosangle)) + cosangle,
+		(n* m * (1 - cosangle)) - (l* sinangle),
+
+		(l* n * (1 - cosangle)) - (m* sinangle),
+		(m* n * (1 - cosangle)) + (l* sinangle),
+		(n* n * (1 - cosangle)) + cosangle
+	});
+
+	// Apply the above matrix to everything
+	for (LDObject* obj : sel)
+	{
+		if (obj->vertices())
+		{
+			for (int i = 0; i < obj->vertices(); ++i)
+			{
+				Vertex v = obj->vertex (i);
+				rotateVertex (v, rotpoint, transform);
+				obj->setVertex (i, v);
+			}
+		}
+		elif (obj->hasMatrix())
+		{
+			LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
+
+			// Transform the position
+			Vertex v = mo->position();
+			rotateVertex (v, rotpoint, transform);
+			mo->setPosition (v);
+
+			// Transform the matrix
+			mo->setTransform (transform * mo->transform());
+		}
+		elif (obj->type() == LDObject::EVertex)
+		{
+			LDVertex* vert = static_cast<LDVertex*> (obj);
+			Vertex v = vert->pos;
+			rotateVertex (v, rotpoint, transform);
+			vert->pos = v;
+		}
+
+		g_win->R()->compileObject (obj);
+	}
+
+	g_win->refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (RotateXPos, CTRL (Right))
+{
+	doRotate (1, 0, 0);
+}
+DEFINE_ACTION (RotateYPos, CTRL (End))
+{
+	doRotate (0, 1, 0);
+}
+DEFINE_ACTION (RotateZPos, CTRL (Up))
+{
+	doRotate (0, 0, 1);
+}
+DEFINE_ACTION (RotateXNeg, CTRL (Left))
+{
+	doRotate (-1, 0, 0);
+}
+DEFINE_ACTION (RotateYNeg, CTRL (Home))
+{
+	doRotate (0, -1, 0);
+}
+DEFINE_ACTION (RotateZNeg, CTRL (Down))
+{
+	doRotate (0, 0, -1);
+}
+
+DEFINE_ACTION (RotationPoint, (0))
+{
+	configRotationPoint();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (RoundCoordinates, 0)
+{
+	setlocale (LC_ALL, "C");
+	int num = 0;
+
+	for (LDObject* obj : selection())
+	{
+		LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
+
+		if (mo != null)
+		{
+			Vertex v = mo->position();
+			Matrix t = mo->transform();
+
+			for_axes (ax)
+				roundToDecimals (v[ax], 3);
+
+			// Let matrix values be rounded to 4 decimals,
+			// they need that extra precision
+			for (int i = 0; i < 9; ++i)
+				roundToDecimals (t[i], 4);
+
+			mo->setPosition (v);
+			mo->setTransform (t);
+			num += 10;
+		}
+		else
+		{
+			for (int i = 0; i < obj->vertices(); ++i)
+			{
+				Vertex v = obj->vertex (i);
+
+				for_axes (ax)
+					roundToDecimals (v[ax], 3);
+
+				obj->setVertex (i, v);
+				R()->compileObject (obj);
+				num += 3;
+			}
+		}
+	}
+
+	print (tr ("Rounded %1 values"), num);
+	refreshObjectList();
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Uncolorize, 0)
+{
+	int num = 0;
+
+	for (LDObject* obj : selection())
+	{
+		if (obj->isColored() == false)
+			continue;
+
+		int col = maincolor;
+
+		if (obj->type() == LDObject::ELine || obj->type() == LDObject::ECondLine)
+			col = edgecolor;
+
+		obj->setColor (col);
+		R()->compileObject (obj);
+		num++;
+	}
+
+	print (tr ("%1 objects uncolored"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (ReplaceCoords, CTRL (R))
+{
+	QDialog* dlg = new QDialog (g_win);
+	Ui::ReplaceCoordsUI ui;
+	ui.setupUi (dlg);
+
+	if (!dlg->exec())
+		return;
+
+	const double search = ui.search->value(),
+		replacement = ui.replacement->value();
+	const bool any = ui.any->isChecked(),
+		rel = ui.relative->isChecked();
+
+	QList<Axis> sel;
+	int num = 0;
+
+	if (ui.x->isChecked()) sel << X;
+	if (ui.y->isChecked()) sel << Y;
+	if (ui.z->isChecked()) sel << Z;
+
+	for (LDObject* obj : selection())
+	{
+		for (int i = 0; i < obj->vertices(); ++i)
+		{
+			Vertex v = obj->vertex (i);
+
+			for (Axis ax : sel)
+			{
+				double& coord = v[ax];
+
+				if (any || coord == search)
+				{
+					if (!rel)
+						coord = 0;
+
+					coord += replacement;
+					num++;
+				}
+			}
+
+			obj->setVertex (i, v);
+			R()->compileObject (obj);
+		}
+	}
+
+	print (tr ("Altered %1 values"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Flip, CTRL_SHIFT (F))
+{
+	QDialog* dlg = new QDialog;
+	Ui::FlipUI ui;
+	ui.setupUi (dlg);
+
+	if (!dlg->exec())
+		return;
+
+	QList<Axis> sel;
+
+	if (ui.x->isChecked()) sel << X;
+	if (ui.y->isChecked()) sel << Y;
+	if (ui.z->isChecked()) sel << Z;
+
+	for (LDObject* obj : selection())
+	{
+		for (int i = 0; i < obj->vertices(); ++i)
+		{
+			Vertex v = obj->vertex (i);
+
+			for (Axis ax : sel)
+				v[ax] *= -1;
+
+			obj->setVertex (i, v);
+			R()->compileObject (obj);
+		}
+	}
+
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Demote, 0)
+{
+	LDObjectList sel = selection();
+	int num = 0;
+
+	for (LDObject* obj : sel)
+	{
+		if (obj->type() != LDObject::ECondLine)
+			continue;
+
+		LDLine* repl = static_cast<LDCondLine*> (obj)->demote();
+		R()->compileObject (repl);
+		++num;
+	}
+
+	print (tr ("Demoted %1 conditional lines"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+static bool isColorUsed (int colnum)
+{
+	for (LDObject* obj : getCurrentDocument()->objects())
+		if (obj->isColored() && obj->color() == colnum)
+			return true;
+
+	return false;
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Autocolor, 0)
+{
+	int colnum = 0;
+
+	while (colnum < MAX_COLORS && (getColor (colnum) == null || isColorUsed (colnum)))
+		colnum++;
+
+	if (colnum >= MAX_COLORS)
+	{
+		print (tr ("Cannot auto-color: all colors are in use!"));
+		return;
+	}
+
+	for (LDObject* obj : selection())
+	{
+		if (obj->isColored() == false)
+			continue;
+
+		obj->setColor (colnum);
+		R()->compileObject (obj);
+	}
+
+	print (tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (AddHistoryLine, 0)
+{
+	LDObject* obj;
+	bool ishistory = false,
+		 prevIsHistory = false;
+
+	QDialog* dlg = new QDialog;
+	Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
+	ui->setupUi (dlg);
+	ui->m_username->setText (ld_defaultuser);
+	ui->m_date->setDate (QDate::currentDate());
+	ui->m_comment->setFocus();
+
+	if (!dlg->exec())
+		return;
+
+	// Create the comment object based on input
+	QString commentText = format ("!HISTORY %1 [%2] %3",
+		ui->m_date->date().toString ("yyyy-MM-dd"),
+		ui->m_username->text(),
+		ui->m_comment->text());
+
+	LDComment* comm = new LDComment (commentText);
+
+	// Find a spot to place the new comment
+	for (
+		obj = getCurrentDocument()->getObject (0);
+		obj && obj->next() && !obj->next()->isScemantic();
+		obj = obj->next()
+	)
+	{
+		LDComment* comm = dynamic_cast<LDComment*> (obj);
+
+		if (comm != null && comm->text().startsWith ("!HISTORY "))
+			ishistory = true;
+
+		if (prevIsHistory && !ishistory)
+		{
+			// Last line was history, this isn't, thus insert the new history
+			// line here.
+			break;
+		}
+
+		prevIsHistory = ishistory;
+	}
+
+	int idx = obj ? obj->lineNumber() : 0;
+	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())
+		getCurrentDocument()->insertObj (idx, new LDEmpty);
+
+	buildObjList();
+	delete ui;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/addObjectDialog.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,443 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QGridLayout>
+#include <QCheckBox>
+#include <QDialogButtonBox>
+#include <QSpinBox>
+#include <QLabel>
+#include <QListWidget>
+#include <QTreeWidget>
+#include <QLineEdit>
+#include <QPushButton>
+#include "mainWindow.h"
+#include "addObjectDialog.h"
+#include "ldDocument.h"
+#include "colors.h"
+#include "colorSelector.h"
+#include "editHistory.h"
+#include "radioGroup.h"
+#include "miscallenous.h"
+#include "primitives.h"
+
+// =============================================================================
+//
+class SubfileListItem : public QTreeWidgetItem
+{
+	PROPERTY (public, Primitive*,	primitive, setPrimitive, STOCK_WRITE)
+
+	public:
+		SubfileListItem (QTreeWidgetItem* parent, Primitive* info) :
+			QTreeWidgetItem (parent),
+			m_primitive (info) {}
+
+		SubfileListItem (QTreeWidget* parent, Primitive* info) :
+			QTreeWidgetItem (parent),
+			m_primitive (info) {}
+};
+
+// =============================================================================
+//
+AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) :
+	QDialog (parent)
+{
+	setlocale (LC_ALL, "C");
+
+	int coordCount = 0;
+	QString typeName = LDObject::typeName (type);
+
+	switch (type)
+	{
+		case LDObject::EComment:
+		{
+			le_comment = new QLineEdit;
+
+			if (obj)
+				le_comment->setText (static_cast<LDComment*> (obj)->text());
+
+			le_comment->setMinimumWidth (384);
+		} break;
+
+		case LDObject::ELine:
+		{
+			coordCount = 6;
+		} break;
+
+		case LDObject::ETriangle:
+		{
+			coordCount = 9;
+		} break;
+
+		case LDObject::EQuad:
+		case LDObject::ECondLine:
+		{
+			coordCount = 12;
+		} break;
+
+		case LDObject::EVertex:
+		{
+			coordCount = 3;
+		} break;
+
+		case LDObject::EBFC:
+		{
+			rb_bfcType = new RadioGroup ("Statement", {}, 0, Qt::Vertical);
+
+			for (int i = 0; i < LDBFC::NumStatements; ++i)
+			{
+				// Separate these in two columns
+				if (i == LDBFC::NumStatements / 2)
+					rb_bfcType->rowBreak();
+
+				rb_bfcType->addButton (LDBFC::k_statementStrings[i]);
+			}
+
+			if (obj)
+				rb_bfcType->setValue ( (int) static_cast<LDBFC*> (obj)->statement());
+		} break;
+
+		case LDObject::ESubfile:
+		{
+			coordCount = 3;
+			tw_subfileList = new QTreeWidget();
+			tw_subfileList->setHeaderLabel (tr ("Primitives"));
+
+			for (PrimitiveCategory* cat : g_PrimitiveCategories)
+			{
+				SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, null);
+				parentItem->setText (0, cat->name());
+				QList<QTreeWidgetItem*> subfileItems;
+
+				for (Primitive& prim : cat->prims)
+				{
+					SubfileListItem* item = new SubfileListItem (parentItem, &prim);
+					item->setText (0, format ("%1 - %2", prim.name, prim.title));
+					subfileItems << item;
+
+					// If this primitive is the one the current object points to,
+					// select it by default
+					if (obj && static_cast<LDSubfile*> (obj)->fileInfo()->name() == prim.name)
+						tw_subfileList->setCurrentItem (item);
+				}
+
+				tw_subfileList->addTopLevelItem (parentItem);
+			}
+
+			connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged()));
+			lb_subfileName = new QLabel ("File:");
+			le_subfileName = new QLineEdit;
+			le_subfileName->setFocus();
+
+			if (obj)
+			{
+				LDSubfile* ref = static_cast<LDSubfile*> (obj);
+				le_subfileName->setText (ref->fileInfo()->name());
+			}
+		} break;
+
+		default:
+		{
+			critical (format ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName));
+		} return;
+	}
+
+	QPixmap icon = getIcon (format ("add-%1", typeName));
+	LDObject* defaults = LDObject::getDefault (type);
+
+	lb_typeIcon = new QLabel;
+	lb_typeIcon->setPixmap (icon);
+
+	// Show a color edit dialog for the types that actually use the color
+	if (defaults->isColored())
+	{
+		if (obj != null)
+			colnum = obj->color();
+		else
+			colnum = (type == LDObject::ECondLine || type == LDObject::ELine) ? edgecolor : maincolor;
+
+		pb_color = new QPushButton;
+		setButtonBackground (pb_color, colnum);
+		connect (pb_color, SIGNAL (clicked()), this, SLOT (slot_colorButtonClicked()));
+	}
+
+	for (int i = 0; i < coordCount; ++i)
+	{
+		dsb_coords[i] = new QDoubleSpinBox;
+		dsb_coords[i]->setDecimals (5);
+		dsb_coords[i]->setMinimum (-10000.0);
+		dsb_coords[i]->setMaximum (10000.0);
+	}
+
+	QGridLayout* const layout = new QGridLayout;
+	layout->addWidget (lb_typeIcon, 0, 0);
+
+	switch (type)
+	{
+		case LDObject::ELine:
+		case LDObject::ECondLine:
+		case LDObject::ETriangle:
+		case LDObject::EQuad:
+
+			// Apply coordinates
+			if (obj)
+			{
+				for (int i = 0; i < coordCount / 3; ++i)
+					for (int j = 0; j < 3; ++j)
+						dsb_coords[ (i * 3) + j]->setValue (obj->vertex (i).getCoordinate (j));
+			}
+
+			break;
+
+		case LDObject::EComment:
+			layout->addWidget (le_comment, 0, 1);
+			break;
+
+		case LDObject::EBFC:
+			layout->addWidget (rb_bfcType, 0, 1);
+			break;
+
+		case LDObject::ESubfile:
+			layout->addWidget (tw_subfileList, 1, 1, 1, 2);
+			layout->addWidget (lb_subfileName, 2, 1);
+			layout->addWidget (le_subfileName, 2, 2);
+			break;
+
+		default:
+			break;
+	}
+
+	if (defaults->hasMatrix())
+	{
+		LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
+
+		QLabel* lb_matrix = new QLabel ("Matrix:");
+		le_matrix = new QLineEdit;
+		// le_matrix->setValidator (new QDoubleValidator);
+		Matrix defaultMatrix = g_identity;
+
+		if (mo)
+		{
+			for_axes (ax)
+				dsb_coords[ax]->setValue (mo->position()[ax]);
+
+			defaultMatrix = mo->transform();
+		}
+
+		le_matrix->setText (defaultMatrix.toString());
+		layout->addWidget (lb_matrix, 4, 1);
+		layout->addWidget (le_matrix, 4, 2, 1, 3);
+	}
+
+	if (defaults->isColored())
+		layout->addWidget (pb_color, 1, 0);
+
+	if (coordCount > 0)
+	{
+		QGridLayout* const qCoordLayout = new QGridLayout;
+
+		for (int i = 0; i < coordCount; ++i)
+			qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3));
+
+		layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3);
+	}
+
+	QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+	QWidget::connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept()));
+	QWidget::connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject()));
+	layout->addWidget (bbx_buttons, 5, 0, 1, 4);
+	setLayout (layout);
+	setWindowTitle (format (tr ("Edit %1"), typeName));
+
+	setWindowIcon (icon);
+	defaults->destroy();
+}
+
+// =============================================================================
+// =============================================================================
+void AddObjectDialog::setButtonBackground (QPushButton* button, int colnum)
+{
+	LDColor* col = ::getColor (colnum);
+
+	button->setIcon (getIcon ("palette"));
+	button->setAutoFillBackground (true);
+
+	if (col)
+		button->setStyleSheet (format ("background-color: %1", col->hexcode));
+}
+
+// =============================================================================
+// =============================================================================
+QString AddObjectDialog::currentSubfileName()
+{
+	SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem());
+
+	if (item->primitive() == null)
+		return ""; // selected a heading
+
+	return item->primitive()->name;
+}
+
+// =============================================================================
+// =============================================================================
+void AddObjectDialog::slot_colorButtonClicked()
+{
+	ColorSelector::selectColor (colnum, colnum, this);
+	setButtonBackground (pb_color, colnum);
+}
+
+// =============================================================================
+// =============================================================================
+void AddObjectDialog::slot_subfileTypeChanged()
+{
+	QString name = currentSubfileName();
+
+	if (name.length() > 0)
+		le_subfileName->setText (name);
+}
+
+// =============================================================================
+// =============================================================================
+template<class T> static T* initObj (LDObject*& obj)
+{
+	if (obj == null)
+		obj = new T;
+
+	return static_cast<T*> (obj);
+}
+
+// =============================================================================
+// =============================================================================
+void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj)
+{
+	setlocale (LC_ALL, "C");
+
+	// FIXME: Redirect to Edit Raw
+	if (obj && obj->type() == LDObject::EError)
+		return;
+
+	if (type == LDObject::EEmpty)
+		return; // Nothing to edit with empties
+
+	const bool newObject = (obj == null);
+	Matrix transform = g_identity;
+	AddObjectDialog dlg (type, obj);
+
+	assert (obj == null || obj->type() == type);
+
+	if (dlg.exec() == false)
+		return;
+
+	if (type == LDObject::ESubfile)
+	{
+		QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts);
+
+		if (matrixstrvals.size() == 9)
+		{
+			double matrixvals[9];
+			int i = 0;
+
+			for (QString val : matrixstrvals)
+				matrixvals[i++] = val.toFloat();
+
+			transform = Matrix (matrixvals);
+		}
+	}
+
+	switch (type)
+	{
+		case LDObject::EComment:
+		{
+			LDComment* comm = initObj<LDComment> (obj);
+			comm->setText (dlg.le_comment->text());
+		}
+		break;
+
+		case LDObject::ELine:
+		case LDObject::ETriangle:
+		case LDObject::EQuad:
+		case LDObject::ECondLine:
+		{
+			if (!obj)
+				obj = LDObject::getDefault (type);
+
+			for (int i = 0; i < obj->vertices(); ++i)
+			{
+				Vertex v;
+
+				for_axes (ax)
+					v[ax] = dlg.dsb_coords[ (i * 3) + ax]->value();
+
+				obj->setVertex (i, v);
+			}
+		} break;
+
+		case LDObject::EBFC:
+		{
+			LDBFC* bfc = initObj<LDBFC> (obj);
+			bfc->setStatement ((LDBFC::Statement) dlg.rb_bfcType->value());
+		} break;
+
+		case LDObject::EVertex:
+		{
+			LDVertex* vert = initObj<LDVertex> (obj);
+
+			for_axes (ax)
+				vert->pos[ax] = dlg.dsb_coords[ax]->value();
+		}
+		break;
+
+		case LDObject::ESubfile:
+		{
+			QString name = dlg.le_subfileName->text();
+
+			if (name.length() == 0)
+				return; // no subfile filename
+
+			LDDocument* file = getDocument (name);
+
+			if (!file)
+			{
+				critical (format ("Couldn't open `%1': %2", name, strerror (errno)));
+				return;
+			}
+
+			LDSubfile* ref = initObj<LDSubfile> (obj);
+			assert (ref);
+
+			for_axes (ax)
+				ref->setCoordinate (ax, dlg.dsb_coords[ax]->value());
+
+			ref->setTransform (transform);
+			ref->setFileInfo (file);
+		} break;
+
+		default:
+			break;
+	}
+
+	if (obj->isColored())
+		obj->setColor (dlg.colnum);
+
+	if (newObject)
+	{
+		int idx = g_win->getInsertionPoint();
+		getCurrentDocument()->insertObj (idx, obj);
+	}
+
+	g_win->refresh();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/addObjectDialog.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,69 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QDialog>
+#include "ldObject.h"
+
+class QTreeWidgetItem;
+class QLineEdit;
+class RadioGroup;
+class QCheckBox;
+class QSpinBox;
+class QLabel;
+class QTreeWidget;
+class QDoubleSpinBox;
+
+class AddObjectDialog : public QDialog
+{
+	Q_OBJECT
+
+	public:
+		AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent = null);
+		static void staticDialog (const LDObject::Type type, LDObject* obj);
+
+		QLabel* lb_typeIcon;
+
+		// Comment line edit
+		QLineEdit* le_comment;
+
+		// Coordinate edits for.. anything with coordinates, really.
+		QDoubleSpinBox* dsb_coords[12];
+
+		// Color selection dialog button
+		QPushButton* pb_color;
+
+		// BFC-related widgets
+		RadioGroup* rb_bfcType;
+
+		// Subfile stuff
+		QTreeWidget* tw_subfileList;
+		QLineEdit* le_subfileName;
+		QLabel* lb_subfileName;
+		QLineEdit* le_matrix;
+
+	private:
+		void setButtonBackground (QPushButton* button, int color);
+		QString currentSubfileName();
+
+		int colnum;
+
+	private slots:
+		void slot_colorButtonClicked();
+		void slot_subfileTypeChanged();
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/basics.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,403 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QObject>
+#include <QStringList>
+#include <QTextStream>
+#include <QFile>
+#include <assert.h>
+#include "main.h"
+#include "basics.h"
+#include "miscallenous.h"
+#include "ldObject.h"
+#include "ldDocument.h"
+
+// =============================================================================
+//
+Vertex::Vertex (double x, double y, double z)
+{
+	m_coords[X] = x;
+	m_coords[Y] = y;
+	m_coords[Z] = z;
+}
+
+// =============================================================================
+//
+void Vertex::move (const Vertex& other)
+{
+	for_axes (ax)
+		m_coords[ax] += other[ax];
+}
+
+// =============================================================================
+//
+double Vertex::distanceTo (const Vertex& other) const
+{
+	double dx = abs (x() - other.x());
+	double dy = abs (y() - other.y());
+	double dz = abs (z() - other.z());
+	return sqrt ((dx * dx) + (dy * dy) + (dz * dz));
+}
+
+// =============================================================================
+//
+Vertex Vertex::midpoint (const Vertex& other)
+{
+	Vertex mid;
+
+	for_axes (ax)
+		mid[ax] = (getCoordinate (ax) + other[ax]) / 2;
+
+	return mid;
+}
+
+// =============================================================================
+//
+QString Vertex::toString (bool mangled) const
+{
+	QString formatstr = "%1 %2 %3";
+
+	if (mangled)
+		formatstr = "(%1, %2, %3)";
+
+	return format (formatstr, x(), y(), z());
+}
+
+// =============================================================================
+//
+void Vertex::transform (const Matrix& matr, const Vertex& pos)
+{
+	double x2 = (matr[0] * x()) + (matr[1] * y()) + (matr[2] * z()) + pos[X];
+	double y2 = (matr[3] * x()) + (matr[4] * y()) + (matr[5] * z()) + pos[Y];
+	double z2 = (matr[6] * x()) + (matr[7] * y()) + (matr[8] * z()) + pos[Z];
+
+	x() = x2;
+	y() = y2;
+	z() = z2;
+}
+
+// =============================================================================
+//
+Vertex Vertex::operator-() const
+{
+	return Vertex (-m_coords[X], -m_coords[Y], -m_coords[Z]);
+}
+
+// =============================================================================
+//
+bool Vertex::operator!= (const Vertex& other) const
+{
+	return !operator== (other);
+}
+
+// =============================================================================
+//
+bool Vertex::operator== (const Vertex& other) const
+{
+	return getCoordinate (X) == other[X] &&
+		   getCoordinate (Y) == other[Y] &&
+		   getCoordinate (Z) == other[Z];
+}
+
+// =============================================================================
+//
+Vertex& Vertex::operator/= (const double d)
+{
+	for_axes (ax)
+		m_coords[ax] /= d;
+
+	return *this;
+}
+
+// =============================================================================
+//
+Vertex Vertex::operator/ (const double d) const
+{
+	Vertex other (*this);
+	return other /= d;
+}
+
+// =============================================================================
+//
+Vertex& Vertex::operator+= (const Vertex& other)
+{
+	move (other);
+	return *this;
+}
+
+// =============================================================================
+//
+Vertex Vertex::operator+ (const Vertex& other) const
+{
+	Vertex newvert (*this);
+	newvert.move (other);
+	return newvert;
+}
+
+// =============================================================================
+//
+int Vertex::operator< (const Vertex& other) const
+{
+	if (operator== (other))
+		return false;
+
+	if (getCoordinate (X) < other[X])
+		return true;
+
+	if (getCoordinate (X) > other[X])
+		return false;
+
+	if (getCoordinate (Y) < other[Y])
+		return true;
+
+	if (getCoordinate (Y) > other[Y])
+		return false;
+
+	return getCoordinate (Z) < other[Z];
+}
+
+// =============================================================================
+//
+Matrix::Matrix (double vals[])
+{
+	for (int i = 0; i < 9; ++i)
+		m_vals[i] = vals[i];
+}
+
+// =============================================================================
+//
+Matrix::Matrix (double fillval)
+{
+	for (int i = 0; i < 9; ++i)
+		m_vals[i] = fillval;
+}
+
+// =============================================================================
+//
+Matrix::Matrix (const std::initializer_list< double >& vals)
+{
+	assert (vals.size() == 9);
+	memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals);
+}
+
+// =============================================================================
+//
+void Matrix::dump() const
+{
+	for (int i = 0; i < 3; ++i)
+	{
+		for (int j = 0; j < 3; ++j)
+			print ("%1\t", m_vals[ (i * 3) + j]);
+
+		print ("\n");
+	}
+}
+
+// =============================================================================
+//
+QString Matrix::toString() const
+{
+	QString val;
+
+	for (int i = 0; i < 9; ++i)
+	{
+		if (i > 0)
+			val += ' ';
+
+		val += QString::number (m_vals[i]);
+	}
+
+	return val;
+}
+
+// =============================================================================
+//
+void Matrix::zero()
+{
+	memset (&m_vals[0], 0, sizeof m_vals);
+}
+
+// =============================================================================
+//
+Matrix Matrix::mult (const Matrix& other) const
+{
+	Matrix val;
+	val.zero();
+
+	for (int i = 0; i < 3; ++i)
+	for (int j = 0; j < 3; ++j)
+	for (int k = 0; k < 3; ++k)
+		val[(i * 3) + j] += m_vals[(i * 3) + k] * other[(k * 3) + j];
+
+	return val;
+}
+
+// =============================================================================
+//
+Matrix& Matrix::operator= (const Matrix& other)
+{
+	memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals);
+	return *this;
+}
+
+// =============================================================================
+//
+double Matrix::getDeterminant() const
+{
+	return (value (0) * value (4) * value (8)) +
+		   (value (1) * value (5) * value (6)) +
+		   (value (2) * value (3) * value (7)) -
+		   (value (2) * value (4) * value (6)) -
+		   (value (1) * value (3) * value (8)) -
+		   (value (0) * value (5) * value (7));
+}
+
+// =============================================================================
+//
+bool Matrix::operator== (const Matrix& other) const
+{
+	for (int i = 0; i < 9; ++i)
+		if (value (i) != other[i])
+			return false;
+
+	return true;
+}
+
+// =============================================================================
+//
+LDBoundingBox::LDBoundingBox()
+{
+	reset();
+}
+
+// =============================================================================
+//
+void LDBoundingBox::calculateFromCurrentDocument()
+{
+	reset();
+
+	if (!getCurrentDocument())
+		return;
+
+	for (LDObject* obj : getCurrentDocument()->objects())
+		calcObject (obj);
+}
+
+// =============================================================================
+//
+void LDBoundingBox::calcObject (LDObject* obj)
+{
+	switch (obj->type())
+	{
+		case LDObject::ELine:
+		case LDObject::ETriangle:
+		case LDObject::EQuad:
+		case LDObject::ECondLine:
+		{
+			for (int i = 0; i < obj->vertices(); ++i)
+				calcVertex (obj->vertex (i));
+		} break;
+
+		case LDObject::ESubfile:
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline);
+
+			for (LDObject * obj : objs)
+			{
+				calcObject (obj);
+				obj->destroy();
+			}
+		}
+		break;
+
+		default:
+			break;
+	}
+}
+
+// =============================================================================
+//
+LDBoundingBox& LDBoundingBox::operator<< (const Vertex& v)
+{
+	calcVertex (v);
+	return *this;
+}
+
+// =============================================================================
+//
+LDBoundingBox& LDBoundingBox::operator<< (LDObject* obj)
+{
+	calcObject (obj);
+	return *this;
+}
+
+// =============================================================================
+//
+void LDBoundingBox::calcVertex (const Vertex& vertex)
+{
+	for_axes (ax)
+	{
+		m_vertex0[ax] = min (vertex[ax], m_vertex0[ax]);
+		m_vertex1[ax] = max (vertex[ax], m_vertex1[ax]);
+	}
+
+	setEmpty (false);
+}
+
+// =============================================================================
+//
+void LDBoundingBox::reset()
+{
+	m_vertex0[X] = m_vertex0[Y] = m_vertex0[Z] = 10000.0;
+	m_vertex1[X] = m_vertex1[Y] = m_vertex1[Z] = -10000.0;
+	setEmpty (true);
+}
+
+// =============================================================================
+//
+double LDBoundingBox::longestMeasurement() const
+{
+	double xscale = (m_vertex0[X] - m_vertex1[X]);
+	double yscale = (m_vertex0[Y] - m_vertex1[Y]);
+	double zscale = (m_vertex0[Z] - m_vertex1[Z]);
+	double size = zscale;
+
+	if (xscale > yscale)
+	{
+		if (xscale > zscale)
+			size = xscale;
+	}
+	elif (yscale > zscale)
+		size = yscale;
+
+	if (abs (size) >= 2.0f)
+		return abs (size / 2);
+
+	return 1.0f;
+}
+
+// =============================================================================
+//
+Vertex LDBoundingBox::center() const
+{
+	return Vertex (
+		(m_vertex0[X] + m_vertex1[X]) / 2,
+		(m_vertex0[Y] + m_vertex1[Y]) / 2,
+		(m_vertex0[Z] + m_vertex1[Z]) / 2);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/basics.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,335 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QString>
+#include <QObject>
+#include <QStringList>
+#include <QMetaType>
+#include "macros.h"
+
+class LDObject;
+class QFile;
+class QTextStream;
+
+using int8 = qint8;
+using int16 = qint16;
+using int32 = qint32;
+using int64 = qint64;
+using uint8 = quint8;
+using uint16 = quint16;
+using uint32 = quint32;
+using uint64 = quint64;
+
+template<typename T, typename R>
+using Pair = std::pair<T, R>;
+
+enum Axis
+{
+	X,
+	Y,
+	Z
+};
+
+// =============================================================================
+//
+class LDObject;
+using LDObjectList = QList<LDObject*>;
+
+//!
+//! \brief A mathematical 3 x 3 matrix
+//!
+class Matrix
+{
+	public:
+		//! Constructs a matrix with undetermined values.
+		Matrix() {}
+
+		//! Constructs a matrix with the given values.
+		//! \note \c vals is expected to have exactly 9 elements.
+		Matrix (const std::initializer_list<double>& vals);
+
+		//! Constructs a matrix all 9 elements initialized to the same value.
+		//! \param fillval the value to initialize the matrix coordinates as
+		Matrix (double fillval);
+
+		//! Constructs a matrix with a C-array.
+		//! \note \c vals is expected to have exactly 9 elements.
+		Matrix (double vals[]);
+
+		//! Calculates the matrix's determinant.
+		//! \returns the calculated determinant.
+		double			getDeterminant() const;
+
+		//! Multiplies this matrix with \c other
+		//! \param other the matrix to multiply with.
+		//! \returns the resulting matrix
+		//! \note a.mult(b) is not equivalent to b.mult(a)!
+		Matrix			mult (const Matrix& other) const;
+
+		//! Prints the matrix to stdout.
+		void			dump() const;
+
+		//! \returns a string representation of the matrix.
+		QString			toString() const;
+
+		//! Zeroes the matrix out.
+		void			zero();
+
+		//! Assigns the matrix values to the values of \c other.
+		//! \param other the matrix to assign this to.
+		//! \returns a reference to self
+		Matrix&			operator= (const Matrix& other);
+
+		//! \returns a mutable reference to a value by \c idx
+		inline double& value (int idx)
+		{
+			return m_vals[idx];
+		}
+
+		//! An overload of \c value() for const matrices.
+		//! \returns a const reference to a value by \c idx
+		inline const double& value (int idx) const
+		{
+			return m_vals[idx];
+		}
+
+		//! An operator overload for \c mult().
+		//! \returns the multiplied matrix.
+		inline Matrix operator* (const Matrix& other) const
+		{
+			return mult (other);
+		}
+
+		//! An operator overload for \c value().
+		//! \returns a mutable reference to a value by \c idx
+		inline double& operator[] (int idx)
+		{
+			return value (idx);
+		}
+
+		//! An operator overload for \c value() const.
+		//! \returns a const reference to a value by \c idx
+		inline const double& operator[] (int idx) const
+		{
+			return value (idx);
+		}
+
+		//! \param other the matrix to check against
+		//! \returns whether the two matrices have the same values.
+		bool operator== (const Matrix& other) const;
+
+	private:
+		double m_vals[9];
+};
+
+//!
+//! \brief A vertex in 3D space
+//!
+//! Contains a single point in 3D space. Not to be confused with
+//! LDVertex, which is a vertex used in an LDraw part file.
+//!
+//! This also sees use as a position vector.
+//!
+class Vertex
+{
+	public:
+		//! Constructs a zero vertex
+		Vertex() :
+			m_coords{0, 0, 0} {}
+
+		//! Constructs a vertex with the given \c x, \c y and \c z.
+		Vertex (double x, double y, double z);
+
+		//! \returns the distance from this vertex to \c other
+		double			distanceTo (const Vertex& other) const;
+
+		//! \returns the vertex at the midpoint between this and \c other
+		Vertex			midpoint (const Vertex& other);
+
+		//! Moves this vertex using \param other as a position vector.
+		void			move (const Vertex& other);
+
+		//! Yields a string representation of the vertex. The string returned
+		//! can possibly be mangled.
+		//! - As mangled: {1.5, 2.8, 3.14}
+		//! - Without mangling: 1.5 2.8 3.14
+		//!
+		//! The mangled version is suitable for printing to the user, the
+		//! non-mangled one is used when writing the vertex to LDraw files.
+		//!
+		//! \returns a string representation of this vertex
+		//! \param mangled whether to return a mangled representation or not
+		QString			toString (bool mangled) const;
+
+		//! Transforms this vertex with \c matr as transformation matrix
+		//! and \c pos as the position column of the 4x4 matrix.
+		void			transform (const Matrix& matr, const Vertex& pos);
+
+		//! An operator overload for \c move().
+		Vertex&			operator+= (const Vertex& other);
+
+		//! An operator overload for \c move(), using a temporary vertex.
+		Vertex			operator+ (const Vertex& other) const;
+
+		//! Divides all values by \c d.
+		Vertex			operator/ (const double d) const;
+
+		//! Divides all values by \c d.
+		Vertex&			operator/= (const double d);
+
+		//! Checks whether this vertex has the same values as \c other.
+		bool			operator== (const Vertex& other) const;
+
+		//! Checks whether this vertex has different values than \c other.
+		bool			operator!= (const Vertex& other) const;
+
+		//! \returns a negated version the vertex
+		Vertex			operator-() const;
+
+		//! \returns whether the vertex has lesser values than \c other.
+		int				operator< (const Vertex& other) const;
+
+		//! An operator overload for \c getCoordinate().
+		inline double& operator[] (const Axis ax)
+		{
+			return getCoordinate ((int) ax);
+		}
+
+		//! An operator overload for \c getCoordinate() const.
+		inline const double& operator[] (const Axis ax) const
+		{
+			return getCoordinate ((int) ax);
+		}
+
+		//! An operator overload for \c getCoordinate().
+		inline double& operator[] (const int ax)
+		{
+			return getCoordinate (ax);
+		}
+
+		//! An operator overload for \c getCoordinate() const.
+		inline const double& operator[] (const int ax) const
+		{
+			return getCoordinate (ax);
+		}
+
+		//! \returns a mutable reference for the coordinate designated by \param n.
+		inline double& getCoordinate (int n)
+		{
+			return m_coords[n];
+		}
+
+		//! An overload of \c getCoordinate for const vertices.
+		//! \returns a const reference for the coordinate designated by \param n.
+		inline const double& getCoordinate (int n) const
+		{
+			return m_coords[n];
+		}
+
+		//! \returns a mutable reference to X.
+		inline double& x()
+		{
+			return m_coords[X];
+		}
+
+		//! An overload of \c x() for const vertices.
+		//! \returns a const reference to X.
+		inline const double& x() const
+		{
+			return m_coords[X];
+		}
+
+		//! \returns a mutable reference to Y.
+		inline double& y()
+		{
+			return m_coords[Y];
+		}
+
+		//! An overload of \c y() for const vertices.
+		//! \returns a const reference to Y.
+		inline const double& y() const
+		{
+			return m_coords[Y];
+		}
+
+		//! \returns a mutable reference to Z.
+		inline double& z()
+		{
+			return m_coords[Z];
+		}
+
+		//! An overload of \c z() for const vertices.
+		//! \returns a const reference to Z.
+		inline const double& z() const
+		{
+			return m_coords[Z];
+		}
+
+	private:
+		double m_coords[3];
+};
+
+Q_DECLARE_METATYPE (Vertex)
+
+//!
+//! Defines a bounding box that encompasses a given set of objects.
+//! vertex0 is the minimum vertex, vertex1 is the maximum vertex.
+//
+class LDBoundingBox
+{
+	PROPERTY (private,	bool,		isEmpty,	setEmpty,		STOCK_WRITE)
+	PROPERTY (private,	Vertex,		vertex0,	setVertex0,		STOCK_WRITE)
+	PROPERTY (private,	Vertex,		vertex1,	setVertex1,		STOCK_WRITE)
+
+	public:
+		//! Constructs an empty bounding box.
+		LDBoundingBox();
+
+		//! Clears the bounding box
+		void reset();
+
+		//! Calculates the bounding box's values from the objects in the current
+		//! document.
+		void calculateFromCurrentDocument();
+
+		//! \returns the length of the bounding box on the longest measure.
+		double longestMeasurement() const;
+
+		//! Calculates the given \c obj to the bounding box, adjusting
+		//! extremas if necessary.
+		void calcObject (LDObject* obj);
+
+		//! Calculates the given \c vertex to the bounding box, adjusting
+		//! extremas if necessary.
+		void calcVertex (const Vertex& vertex);
+
+		//! \returns the center of the bounding box.
+		Vertex center() const;
+
+		//! An operator overload for \c calcObject()
+		LDBoundingBox& operator<< (LDObject* obj);
+
+		//! An operator overload for \c calcVertex()
+		LDBoundingBox& operator<< (const Vertex& v);
+};
+
+extern const Vertex g_origin; // Vertex at (0, 0, 0)
+extern const Matrix g_identity; // Identity matrix
+
+static const double pi = 3.14159265358979323846;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colorSelector.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,210 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  colorSelectDialog.cxx: Color selector box.
+ */
+
+#include <QGraphicsScene>
+#include <QGraphicsItem>
+#include <QMouseEvent>
+#include <QScrollBar>
+
+#include "main.h"
+#include "mainWindow.h"
+#include "colorSelector.h"
+#include "colors.h"
+#include "configuration.h"
+#include "miscallenous.h"
+#include "ui_colorsel.h"
+
+static const int g_numColumns = 16;
+static const int g_squareSize = 32;
+
+extern_cfg (String, gl_maincolor);
+extern_cfg (Float, gl_maincolor_alpha);
+
+// =============================================================================
+// =============================================================================
+ColorSelector::ColorSelector (int defval, QWidget* parent) : QDialog (parent)
+{
+	// Remove the default color if it's invalid
+	if (!getColor (defval))
+		defval = -1;
+
+	m_firstResize = true;
+	ui = new Ui_ColorSelUI;
+	ui->setupUi (this);
+
+	m_scene = new QGraphicsScene;
+	ui->viewport->setScene (m_scene);
+	setSelection (getColor (defval));
+
+	// not really an icon but eh
+	m_scene->setBackgroundBrush (getIcon ("checkerboard"));
+	drawScene();
+
+	int width = viewportWidth();
+	ui->viewport->setMinimumWidth (width);
+	ui->viewport->setMaximumWidth (width);
+
+	drawColorInfo();
+}
+
+// =============================================================================
+// =============================================================================
+ColorSelector::~ColorSelector()
+{
+	delete ui;
+}
+
+// =============================================================================
+// =============================================================================
+void ColorSelector::drawScene()
+{
+	const int numCols = g_numColumns;
+	const int square = g_squareSize;
+	const int g_maxHeight = (numRows() * square);
+	QRect sceneRect (0, 0, viewportWidth(), g_maxHeight);
+
+	m_scene->setSceneRect (sceneRect);
+	ui->viewport->setSceneRect (sceneRect);
+
+	const double penWidth = 1.0f;
+
+	// Draw the color rectangles.
+	m_scene->clear();
+
+	for (int i = 0; i < MAX_COLORS; ++i)
+	{
+		LDColor* info = ::getColor (i);
+
+		if (!info)
+			continue;
+
+		const double x = (i % numCols) * square;
+		const double y = (i / numCols) * square;
+		const double w = square - (penWidth / 2);
+
+		QColor col = info->faceColor;
+
+		if (i == maincolor)
+		{
+			// Use the user preferences for main color here
+			col = QColor (gl_maincolor);
+			col.setAlpha (gl_maincolor_alpha * 255.0f);
+		}
+
+		QPen pen (info->edgeColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
+		m_scene->addRect (x, y, w, w, pen, col);
+		QGraphicsTextItem* numtext = m_scene->addText (format ("%1", i));
+		numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black);
+		numtext->setPos (x, y);
+
+		if (selection() && i == selection()->index)
+		{
+			auto curspic = m_scene->addPixmap (getIcon ("colorcursor"));
+			curspic->setPos (x, y);
+		}
+	}
+}
+
+// =============================================================================
+// =============================================================================
+int ColorSelector::numRows() const
+{
+	return (MAX_COLORS / g_numColumns);
+}
+
+// =============================================================================
+// =============================================================================
+int ColorSelector::viewportWidth() const
+{
+	return g_numColumns * g_squareSize + 21;
+}
+
+// =============================================================================
+// =============================================================================
+void ColorSelector::drawColorInfo()
+{
+	if (!selection())
+	{
+		ui->colorLabel->setText ("---");
+		return;
+	}
+
+	ui->colorLabel->setText (format ("%1 - %2", selection()->index, selection()->name));
+}
+
+// =============================================================================
+// =============================================================================
+void ColorSelector::resizeEvent (QResizeEvent* ev)
+{
+	// If this is the first resize, check if we need to scroll down to see the
+	// currently selected color. We cannot do this in the constructor because the
+	// height is not set properly there.
+	if (m_firstResize)
+	{
+		int visibleColors = (ui->viewport->height() / g_squareSize) * g_numColumns;
+
+		if (selection() && selection()->index >= visibleColors)
+		{
+			int y = (selection()->index / g_numColumns) * g_squareSize;
+			ui->viewport->verticalScrollBar()->setValue (y);
+		}
+
+		m_firstResize = false;
+	}
+
+	(void) ev;
+	drawScene();
+}
+
+// =============================================================================
+// =============================================================================
+void ColorSelector::mousePressEvent (QMouseEvent* event)
+{
+	QPointF scenepos = ui->viewport->mapToScene (event->pos());
+
+	int x = (scenepos.x() - (g_squareSize / 2)) / g_squareSize;
+	int y = (scenepos.y() - (g_squareSize / 2)) / g_squareSize;
+	int idx = (y * g_numColumns) + x;
+
+	LDColor* col = ::getColor (idx);
+
+	if (!col)
+		return;
+
+	setSelection (col);
+	drawScene();
+	drawColorInfo();
+}
+
+// =============================================================================
+// =============================================================================
+bool ColorSelector::selectColor (int& val, int defval, QWidget* parent)
+{
+	ColorSelector dlg (defval, parent);
+
+	if (dlg.exec() && dlg.selection() != null)
+	{
+		val = dlg.selection()->index;
+		return true;
+	}
+
+	return false;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colorSelector.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,50 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QDialog>
+#include "main.h"
+
+class LDColor;
+class Ui_ColorSelUI;
+class QGraphicsScene;
+
+class ColorSelector : public QDialog
+{
+	Q_OBJECT
+	PROPERTY (private,	LDColor*,	selection,	setSelection,	STOCK_WRITE)
+
+	public:
+		explicit ColorSelector (int defval = -1, QWidget* parent = null);
+		virtual ~ColorSelector();
+		static bool selectColor (int& val, int defval = -1, QWidget* parent = null);
+
+	protected:
+		void mousePressEvent (QMouseEvent* event);
+		void resizeEvent (QResizeEvent* ev);
+
+	private:
+		Ui_ColorSelUI* ui;
+		QGraphicsScene* m_scene;
+		bool m_firstResize;
+
+		int numRows() const;
+		int viewportWidth() const;
+		void drawScene();
+		void drawColorInfo();
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colors.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,82 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  colors.cxx: LDraw color management. LDConfig.ldr parsing is not here!
+ *  TODO: Make LDColor more full-fledged, add support for direct colors.
+ *  TODO: g_LDColors should probably be a map.
+ */
+
+#include "main.h"
+#include "colors.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "ldConfig.h"
+#include <QColor>
+
+static LDColor* g_LDColors[MAX_COLORS];
+
+// =============================================================================
+// =============================================================================
+void initColors()
+{
+	LDColor* col;
+	print ("Initializing color information.\n");
+
+	// Always make sure there's 16 and 24 available. They're special like that.
+	col = new LDColor;
+	col->faceColor = col->hexcode = "#AAAAAA";
+	col->edgeColor = Qt::black;
+	g_LDColors[maincolor] = col;
+
+	col = new LDColor;
+	col->faceColor = col->edgeColor = col->hexcode = "#000000";
+	g_LDColors[edgecolor] = col;
+
+	parseLDConfig();
+}
+
+// =============================================================================
+// =============================================================================
+LDColor* getColor (int colnum)
+{
+	// Check bounds
+	if (colnum < 0 || colnum >= MAX_COLORS)
+		return null;
+
+	return g_LDColors[colnum];
+}
+
+// =============================================================================
+// =============================================================================
+void setColor (int colnum, LDColor* col)
+{
+	if (colnum < 0 || colnum >= MAX_COLORS)
+		return;
+
+	g_LDColors[colnum] = col;
+}
+
+// =============================================================================
+// =============================================================================
+int luma (QColor& col)
+{
+	return (0.2126f * col.red()) +
+		   (0.7152f * col.green()) +
+		   (0.0722f * col.blue());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colors.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,42 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QColor>
+#include "main.h"
+
+#define MAX_COLORS 512
+
+class LDColor
+{
+	public:
+		QString name, hexcode;
+		QColor faceColor, edgeColor;
+		int index;
+};
+
+void initColors();
+int luma (QColor& col);
+
+// Safely gets a color with the given number or null if no such color.
+LDColor* getColor (int colnum);
+void setColor (int colnum, LDColor* col);
+
+// Main and edge color identifiers
+static const int maincolor = 16;
+static const int edgecolor = 24;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/configDialog.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,803 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  configDialog.cxx: Settings dialog and everything related to it.
+ *  Actual configuration core is in config.cxx.
+ */
+
+#include <QGridLayout>
+#include <QFileDialog>
+#include <QColorDialog>
+#include <QBoxLayout>
+#include <QKeyEvent>
+#include <QGroupBox>
+#include <QDoubleSpinBox>
+#include <QLineEdit>
+#include <QCheckBox>
+#include "main.h"
+#include "configDialog.h"
+#include "ldDocument.h"
+#include "configuration.h"
+#include "miscallenous.h"
+#include "colors.h"
+#include "colorSelector.h"
+#include "glRenderer.h"
+#include "ui_config.h"
+
+extern_cfg (String, gl_bgcolor);
+extern_cfg (String, gl_maincolor);
+extern_cfg (Bool, lv_colorize);
+extern_cfg (Bool, gl_colorbfc);
+extern_cfg (Float, gl_maincolor_alpha);
+extern_cfg (Int, gl_linethickness);
+extern_cfg (String, gui_colortoolbar);
+extern_cfg (Bool, edit_schemanticinline);
+extern_cfg (Bool, gl_blackedges);
+extern_cfg (Bool, gl_aa);
+extern_cfg (Bool, gui_implicitfiles);
+extern_cfg (String, net_downloadpath);
+extern_cfg (Bool, net_guesspaths);
+extern_cfg (Bool, net_autoclose);
+extern_cfg (Bool, gl_logostuds);
+extern_cfg (Bool,	gl_linelengths);
+extern_cfg (String, ld_defaultname);
+extern_cfg (String, ld_defaultuser);
+extern_cfg (Int, ld_defaultlicense);
+extern_cfg (String, gl_selectcolor);
+extern_cfg (String, prog_ytruder);
+extern_cfg (String, prog_rectifier);
+extern_cfg (String, prog_intersector);
+extern_cfg (String, prog_coverer);
+extern_cfg (String, prog_isecalc);
+extern_cfg (String, prog_edger2);
+extern_cfg (Bool, prog_ytruder_wine);
+extern_cfg (Bool, prog_rectifier_wine);
+extern_cfg (Bool, prog_intersector_wine);
+extern_cfg (Bool, prog_coverer_wine);
+extern_cfg (Bool, prog_isecalc_wine);
+extern_cfg (Bool, prog_edger2_wine);
+
+const char* g_extProgPathFilter =
+#ifdef _WIN32
+	"Applications (*.exe)(*.exe);;All files (*.*)(*.*)";
+#else
+	"";
+#endif
+
+// =============================================================================
+// =============================================================================
+ConfigDialog::ConfigDialog (ConfigDialog::Tab deftab, QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f)
+{
+	assert (g_win != null);
+	ui = new Ui_ConfigUI;
+	ui->setupUi (this);
+
+	// Interface tab
+	setButtonBackground (ui->backgroundColorButton, gl_bgcolor);
+	connect (ui->backgroundColorButton, SIGNAL (clicked()),
+			 this, SLOT (slot_setGLBackground()));
+
+	setButtonBackground (ui->mainColorButton, gl_maincolor);
+	connect (ui->mainColorButton, SIGNAL (clicked()),
+			 this, SLOT (slot_setGLForeground()));
+
+	setButtonBackground (ui->selColorButton, gl_selectcolor);
+	connect (ui->selColorButton, SIGNAL (clicked()),
+			 this, SLOT (slot_setGLSelectColor()));
+
+	ui->mainColorAlpha->setValue (gl_maincolor_alpha * 10.0f);
+	ui->lineThickness->setValue (gl_linethickness);
+	ui->colorizeObjects->setChecked (lv_colorize);
+	ui->colorBFC->setChecked (gl_colorbfc);
+	ui->blackEdges->setChecked (gl_blackedges);
+	ui->m_aa->setChecked (gl_aa);
+	ui->implicitFiles->setChecked (gui_implicitfiles);
+	ui->m_logostuds->setChecked (gl_logostuds);
+	ui->linelengths->setChecked (gl_linelengths);
+
+	int i = 0;
+
+	for (QAction* act : g_win->findChildren<QAction*>())
+	{
+		KeySequenceConfig* cfg = g_win->shortcutForAction (act);
+
+		if (cfg)
+			addShortcut (*cfg, act, i);
+	}
+
+	ui->shortcutsList->setSortingEnabled (true);
+	ui->shortcutsList->sortItems();
+
+	connect (ui->shortcut_set, SIGNAL (clicked()), this, SLOT (slot_setShortcut()));
+	connect (ui->shortcut_reset, SIGNAL (clicked()), this, SLOT (slot_resetShortcut()));
+	connect (ui->shortcut_clear, SIGNAL (clicked()), this, SLOT (slot_clearShortcut()));
+
+	quickColors = quickColorsFromConfig();
+	updateQuickColorList();
+
+	connect (ui->quickColor_add, SIGNAL (clicked()), this, SLOT (slot_setColor()));
+	connect (ui->quickColor_remove, SIGNAL (clicked()), this, SLOT (slot_delColor()));
+	connect (ui->quickColor_edit, SIGNAL (clicked()), this, SLOT (slot_setColor()));
+	connect (ui->quickColor_addSep, SIGNAL (clicked()), this, SLOT (slot_addColorSeparator()));
+	connect (ui->quickColor_moveUp, SIGNAL (clicked()), this, SLOT (slot_moveColor()));
+	connect (ui->quickColor_moveDown, SIGNAL (clicked()), this, SLOT (slot_moveColor()));
+	connect (ui->quickColor_clear, SIGNAL (clicked()), this, SLOT (slot_clearColors()));
+
+	ui->downloadPath->setText (net_downloadpath);
+	ui->guessNetPaths->setChecked (net_guesspaths);
+	ui->autoCloseNetPrompt->setChecked (net_autoclose);
+	connect (ui->findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder()));
+
+	ui->m_profileName->setText (ld_defaultname);
+	ui->m_profileUsername->setText (ld_defaultuser);
+	ui->m_profileLicense->setCurrentIndex (ld_defaultlicense);
+
+	initGrids();
+	initExtProgs();
+	selectPage (deftab);
+
+	connect (ui->buttonBox, SIGNAL (clicked (QAbstractButton*)),
+		this, SLOT (buttonClicked (QAbstractButton*)));
+
+	connect (ui->m_pages, SIGNAL (currentChanged (int)),
+		this, SLOT (selectPage (int)));
+
+	connect (ui->m_pagelist, SIGNAL (currentRowChanged (int)),
+		this, SLOT (selectPage (int)));
+}
+
+// =============================================================================
+// =============================================================================
+ConfigDialog::~ConfigDialog()
+{
+	delete ui;
+}
+
+// =============================================================================
+// =============================================================================
+void ConfigDialog::selectPage (int row)
+{
+	ui->m_pagelist->setCurrentRow (row);
+	ui->m_pages->setCurrentIndex (row);
+}
+
+// =============================================================================
+// Adds a shortcut entry to the list of shortcuts.
+// =============================================================================
+void ConfigDialog::addShortcut (KeySequenceConfig& cfg, QAction* act, int& i)
+{
+	ShortcutListItem* item = new ShortcutListItem;
+	item->setIcon (act->icon());
+	item->setKeyConfig (&cfg);
+	item->setAction (act);
+	setShortcutText (item);
+
+	// If the action doesn't have a valid icon, use an empty one
+	// so that the list is kept aligned.
+	if (act->icon().isNull())
+		item->setIcon (getIcon ("empty"));
+
+	ui->shortcutsList->insertItem (i++, item);
+}
+
+// =============================================================================
+// Initializes the table of grid stuff
+// =============================================================================
+void ConfigDialog::initGrids()
+{
+	QGridLayout* gridlayout = new QGridLayout;
+	QLabel* xlabel = new QLabel ("X"),
+	*ylabel = new QLabel ("Y"),
+	*zlabel = new QLabel ("Z"),
+	*anglabel = new QLabel ("Angle");
+	int i = 1;
+
+	for (QLabel* label : QList<QLabel*> ({xlabel, ylabel, zlabel, anglabel}))
+	{
+		label->setAlignment (Qt::AlignCenter);
+		gridlayout->addWidget (label, 0, i++);
+	}
+
+	for (int i = 0; i < g_NumGrids; ++i)
+	{
+		// Icon
+		lb_gridIcons[i] = new QLabel;
+		lb_gridIcons[i]->setPixmap (getIcon (format ("grid-%1", QString (g_GridInfo[i].name).toLower())));
+
+		// Text label
+		lb_gridLabels[i] = new QLabel (format ("%1:", g_GridInfo[i].name));
+
+		QHBoxLayout* labellayout = new QHBoxLayout;
+		labellayout->addWidget (lb_gridIcons[i]);
+		labellayout->addWidget (lb_gridLabels[i]);
+		gridlayout->addLayout (labellayout, i + 1, 0);
+
+		// Add the widgets
+		for (int j = 0; j < 4; ++j)
+		{
+			dsb_gridData[i][j] = new QDoubleSpinBox;
+
+			// Set the maximum angle
+			if (j == 3)
+				dsb_gridData[i][j]->setMaximum (360);
+
+			dsb_gridData[i][j]->setValue (*g_GridInfo[i].confs[j]);
+			gridlayout->addWidget (dsb_gridData[i][j], i + 1, j + 1);
+		}
+	}
+
+	ui->grids->setLayout (gridlayout);
+}
+
+// =============================================================================
+// =============================================================================
+static struct LDExtProgInfo
+{
+	const QString		name,
+						iconname;
+	QString* const		path;
+	QLineEdit*		input;
+	QPushButton*	setPathButton;
+#ifndef _WIN32
+	bool* const		wine;
+	QCheckBox*		wineBox;
+#endif // _WIN32
+} g_LDExtProgInfo[] =
+{
+#ifndef _WIN32
+# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &prog_##LOWNAME, null, null, &prog_##LOWNAME##_wine, null },
+#else
+# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &prog_##LOWNAME, null, null },
+#endif
+	EXTPROG (Ytruder, ytruder)
+	EXTPROG (Rectifier, rectifier)
+	EXTPROG (Intersector, intersector)
+	EXTPROG (Isecalc, isecalc)
+	EXTPROG (Coverer, coverer)
+	EXTPROG (Edger2, edger2)
+#undef EXTPROG
+};
+
+// =============================================================================
+// Initializes the stuff in the ext programs tab
+// =============================================================================
+void ConfigDialog::initExtProgs()
+{
+	QGridLayout* pathsLayout = new QGridLayout;
+	int row = 0;
+
+	for (LDExtProgInfo& info : g_LDExtProgInfo)
+	{
+		QLabel* icon = new QLabel,
+		*progLabel = new QLabel (info.name);
+		QLineEdit* input = new QLineEdit;
+		QPushButton* setPathButton = new QPushButton;
+
+		icon->setPixmap (getIcon (info.iconname));
+		input->setText (*info.path);
+		setPathButton->setIcon (getIcon ("folder"));
+		info.input = input;
+		info.setPathButton = setPathButton;
+
+		connect (setPathButton, SIGNAL (clicked()), this, SLOT (slot_setExtProgPath()));
+
+		pathsLayout->addWidget (icon, row, 0);
+		pathsLayout->addWidget (progLabel, row, 1);
+		pathsLayout->addWidget (input, row, 2);
+		pathsLayout->addWidget (setPathButton, row, 3);
+
+#ifndef _WIN32
+		QCheckBox* wineBox = new QCheckBox ("Wine");
+		wineBox->setChecked (*info.wine);
+		info.wineBox = wineBox;
+		pathsLayout->addWidget (wineBox, row, 4);
+#endif
+
+		++row;
+	}
+
+	ui->extProgs->setLayout (pathsLayout);
+}
+
+// =============================================================================
+// Set the settings based on widget data.
+// =============================================================================
+void ConfigDialog::applySettings()
+{
+	// Apply configuration
+	lv_colorize = ui->colorizeObjects->isChecked();
+	gl_colorbfc = ui->colorBFC->isChecked();
+	gl_blackedges = ui->blackEdges->isChecked();
+	gl_maincolor_alpha = ( (double) ui->mainColorAlpha->value()) / 10.0f;
+	gl_linethickness = ui->lineThickness->value();
+	gui_implicitfiles = ui->implicitFiles->isChecked();
+	net_downloadpath = ui->downloadPath->text();
+	net_guesspaths = ui->guessNetPaths->isChecked();
+	net_autoclose = ui->autoCloseNetPrompt->isChecked();
+	gl_logostuds = ui->m_logostuds->isChecked();
+	gl_linelengths = ui->linelengths->isChecked();
+	ld_defaultuser = ui->m_profileUsername->text();
+	ld_defaultname = ui->m_profileName->text();
+	ld_defaultlicense = ui->m_profileLicense->currentIndex();
+	gl_aa = ui->m_aa->isChecked();
+
+	// Rebuild the quick color toolbar
+	g_win->setQuickColors (quickColors);
+	gui_colortoolbar = quickColorString();
+
+	// Set the grid settings
+	for (int i = 0; i < g_NumGrids; ++i)
+		for (int j = 0; j < 4; ++j)
+			*g_GridInfo[i].confs[j] = dsb_gridData[i][j]->value();
+
+	// Apply key shortcuts
+	g_win->updateActionShortcuts();
+
+	// Ext program settings
+	for (const LDExtProgInfo& info : g_LDExtProgInfo)
+	{
+		*info.path = info.input->text();
+
+#ifndef _WIN32
+		*info.wine = info.wineBox->isChecked();
+#endif // _WIN32
+	}
+
+	Config::save();
+	reloadAllSubfiles();
+	loadLogoedStuds();
+	g_win->R()->setBackground();
+	g_win->doFullRefresh();
+	g_win->updateDocumentList();
+}
+
+// =============================================================================
+// A dialog button was clicked
+// =============================================================================
+void ConfigDialog::buttonClicked (QAbstractButton* button)
+{
+	typedef QDialogButtonBox QDDB;
+	QDialogButtonBox* dbb = ui->buttonBox;
+
+	if (button == dbb->button (QDDB::Ok))
+	{
+		applySettings();
+		accept();
+	} elif (button == dbb->button (QDDB::Apply))
+	{
+		applySettings();
+	} elif (button == dbb->button (QDDB::Cancel))
+	{
+		reject();
+	}
+}
+
+// =============================================================================
+// Update the list of color toolbar items in the quick color tab.
+// =============================================================================
+void ConfigDialog::updateQuickColorList (LDQuickColor* sel)
+{
+	for (QListWidgetItem * item : quickColorItems)
+		delete item;
+
+	quickColorItems.clear();
+
+	// Init table items
+	for (LDQuickColor& entry : quickColors)
+	{
+		QListWidgetItem* item = new QListWidgetItem;
+
+		if (entry.isSeparator())
+		{
+			item->setText ("--------");
+			item->setIcon (getIcon ("empty"));
+		}
+		else
+		{
+			LDColor* col = entry.color();
+
+			if (col == null)
+			{
+				item->setText ("[[unknown color]]");
+				item->setIcon (getIcon ("error"));
+			}
+			else
+			{
+				item->setText (col->name);
+				item->setIcon (makeColorIcon (col, 16));
+			}
+		}
+
+		ui->quickColorList->addItem (item);
+		quickColorItems << item;
+
+		if (sel && &entry == sel)
+		{
+			ui->quickColorList->setCurrentItem (item);
+			ui->quickColorList->scrollToItem (item);
+		}
+	}
+}
+
+// =============================================================================
+// Quick colors: add or edit button was clicked.
+// =============================================================================
+void ConfigDialog::slot_setColor()
+{
+	LDQuickColor* entry = null;
+	QListWidgetItem* item = null;
+	const bool isNew = static_cast<QPushButton*> (sender()) == ui->quickColor_add;
+
+	if (isNew == false)
+	{
+		item = getSelectedQuickColor();
+
+		if (!item)
+			return;
+
+		int i = getItemRow (item, quickColorItems);
+		entry = &quickColors[i];
+
+		if (entry->isSeparator() == true)
+			return; // don't color separators
+	}
+
+	int defval = entry ? entry->color()->index : -1;
+	int val;
+
+	if (ColorSelector::selectColor (val, defval, this) == false)
+		return;
+
+	if (entry)
+		entry->setColor (getColor (val));
+	else
+	{
+		LDQuickColor entry (getColor (val), null);
+
+		item = getSelectedQuickColor();
+		int idx = (item) ? getItemRow (item, quickColorItems) + 1 : quickColorItems.size();
+
+		quickColors.insert (idx, entry);
+		entry = quickColors[idx];
+	}
+
+	updateQuickColorList (entry);
+}
+
+// =============================================================================
+// Remove a quick color
+// =============================================================================
+void ConfigDialog::slot_delColor()
+{
+	if (ui->quickColorList->selectedItems().isEmpty())
+		return;
+
+	QListWidgetItem* item = ui->quickColorList->selectedItems() [0];
+	quickColors.removeAt (getItemRow (item, quickColorItems));
+	updateQuickColorList();
+}
+
+// =============================================================================
+// Move a quick color up/down
+// =============================================================================
+void ConfigDialog::slot_moveColor()
+{
+	const bool up = (static_cast<QPushButton*> (sender()) == ui->quickColor_moveUp);
+
+	if (ui->quickColorList->selectedItems().isEmpty())
+		return;
+
+	QListWidgetItem* item = ui->quickColorList->selectedItems() [0];
+	int idx = getItemRow (item, quickColorItems);
+	int dest = up ? (idx - 1) : (idx + 1);
+
+	if (dest < 0 || dest >= quickColorItems.size())
+		return; // destination out of bounds
+
+	LDQuickColor tmp = quickColors[dest];
+	quickColors[dest] = quickColors[idx];
+	quickColors[idx] = tmp;
+
+	updateQuickColorList (&quickColors[dest]);
+}
+
+// =============================================================================
+//
+// Add a separator to quick colors
+//
+void ConfigDialog::slot_addColorSeparator()
+{
+	quickColors << LDQuickColor::getSeparator();
+	updateQuickColorList (&quickColors[quickColors.size() - 1]);
+}
+
+// =============================================================================
+//
+// Clear all quick colors
+//
+void ConfigDialog::slot_clearColors()
+{
+	quickColors.clear();
+	updateQuickColorList();
+}
+
+// =============================================================================
+//
+// Pick a color and set the appropriate configuration option.
+//
+void ConfigDialog::pickColor (QString& conf, QPushButton* button)
+{
+	QColor col = QColorDialog::getColor (QColor (conf));
+
+	if (col.isValid())
+	{
+		int r = col.red(),
+			g = col.green(),
+			b = col.blue();
+
+		QString colname;
+		colname.sprintf ("#%.2X%.2X%.2X", r, g, b);
+		conf = colname;
+		setButtonBackground (button, colname);
+	}
+}
+
+// =============================================================================
+// =============================================================================
+void ConfigDialog::slot_setGLBackground()
+{
+	pickColor (gl_bgcolor, ui->backgroundColorButton);
+}
+
+// =============================================================================
+// =============================================================================
+void ConfigDialog::slot_setGLForeground()
+{
+	pickColor (gl_maincolor, ui->mainColorButton);
+}
+
+// =============================================================================
+// =============================================================================
+void ConfigDialog::slot_setGLSelectColor()
+{
+	pickColor (gl_selectcolor, ui->selColorButton);
+}
+
+// =============================================================================
+// Sets background color of a given button.
+// =============================================================================
+void ConfigDialog::setButtonBackground (QPushButton* button, QString value)
+{
+	button->setIcon (getIcon ("colorselect"));
+	button->setAutoFillBackground (true);
+	button->setStyleSheet (format ("background-color: %1", value));
+}
+
+// =============================================================================
+// Finds the given list widget item in the list of widget items given.
+// =============================================================================
+int ConfigDialog::getItemRow (QListWidgetItem* item, QList<QListWidgetItem*>& haystack)
+{
+	int i = 0;
+
+	for (QListWidgetItem* it : haystack)
+	{
+		if (it == item)
+			return i;
+
+		++i;
+	}
+
+	return -1;
+}
+
+// =============================================================================
+// Which quick color is currently selected?
+// =============================================================================
+QListWidgetItem* ConfigDialog::getSelectedQuickColor()
+{
+	if (ui->quickColorList->selectedItems().isEmpty())
+		return null;
+
+	return ui->quickColorList->selectedItems() [0];
+}
+
+// =============================================================================
+// Get the list of shortcuts selected
+// =============================================================================
+QList<ShortcutListItem*> ConfigDialog::getShortcutSelection()
+{
+	QList<ShortcutListItem*> out;
+
+	for (QListWidgetItem* entry : ui->shortcutsList->selectedItems())
+		out << static_cast<ShortcutListItem*> (entry);
+
+	return out;
+}
+
+// =============================================================================
+// Edit the shortcut of a given action.
+// =============================================================================
+void ConfigDialog::slot_setShortcut()
+{
+	QList<ShortcutListItem*> sel = getShortcutSelection();
+
+	if (sel.size() < 1)
+		return;
+
+	ShortcutListItem* item = sel[0];
+
+	if (KeySequenceDialog::staticDialog (item->keyConfig(), this))
+		setShortcutText (item);
+}
+
+// =============================================================================
+// Reset a shortcut to defaults
+// =============================================================================
+void ConfigDialog::slot_resetShortcut()
+{
+	QList<ShortcutListItem*> sel = getShortcutSelection();
+
+	for (ShortcutListItem* item : sel)
+	{
+		item->keyConfig()->reset();
+		setShortcutText (item);
+	}
+}
+
+// =============================================================================
+// Remove the shortcut of an action.
+// =============================================================================
+void ConfigDialog::slot_clearShortcut()
+{
+	QList<ShortcutListItem*> sel = getShortcutSelection();
+
+	for (ShortcutListItem* item : sel)
+	{
+		item->keyConfig()->setValue (QKeySequence());
+		setShortcutText (item);
+	}
+}
+
+// =============================================================================
+// Set the path of an external program
+// =============================================================================
+void ConfigDialog::slot_setExtProgPath()
+{
+	const LDExtProgInfo* info = null;
+
+	for (const LDExtProgInfo& it : g_LDExtProgInfo)
+	{
+		if (it.setPathButton == sender())
+		{
+			info = &it;
+			break;
+		}
+	}
+
+	assert (info != null);
+	QString fpath = QFileDialog::getOpenFileName (this, format ("Path to %1", info->name), *info->path, g_extProgPathFilter);
+
+	if (fpath.isEmpty())
+		return;
+
+	info->input->setText (fpath);
+}
+
+// =============================================================================
+//
+// '...' button pressed for the download path
+//
+void ConfigDialog::slot_findDownloadFolder()
+{
+	QString dpath = QFileDialog::getExistingDirectory();
+	ui->downloadPath->setText (dpath);
+}
+
+// =============================================================================
+//
+// Updates the text string for a given shortcut list item
+//
+void ConfigDialog::setShortcutText (ShortcutListItem* item)
+{
+	QAction* act = item->action();
+	QString label = act->iconText();
+	QString keybind = item->keyConfig()->getValue().toString();
+	item->setText (format ("%1 (%2)", label, keybind));
+}
+
+// =============================================================================
+// Gets the configuration string of the quick color toolbar
+// =============================================================================
+QString ConfigDialog::quickColorString()
+{
+	QString val;
+
+	for (const LDQuickColor& entry : quickColors)
+	{
+		if (val.length() > 0)
+			val += ':';
+
+		if (entry.isSeparator())
+			val += '|';
+		else
+			val += format ("%1", entry.color()->index);
+	}
+
+	return val;
+}
+
+// ===============================================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// ===============================================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// ===============================================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// ===============================================================================================
+KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f), seq (seq)
+{
+	lb_output = new QLabel;
+	IMPLEMENT_DIALOG_BUTTONS
+
+	setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a "
+		"shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to "
+		"dismiss."));
+
+	QVBoxLayout* layout = new QVBoxLayout;
+	layout->addWidget (lb_output);
+	layout->addWidget (bbx_buttons);
+	setLayout (layout);
+
+	updateOutput();
+}
+
+// =============================================================================
+// =============================================================================
+bool KeySequenceDialog::staticDialog (KeySequenceConfig* cfg, QWidget* parent)
+{
+	KeySequenceDialog dlg (cfg->getValue(), parent);
+
+	if (dlg.exec() == false)
+		return false;
+
+	cfg->setValue (dlg.seq);
+	return true;
+}
+
+// =============================================================================
+// =============================================================================
+void KeySequenceDialog::updateOutput()
+{
+	QString shortcut = seq.toString();
+
+	if (seq == QKeySequence())
+		shortcut = "&lt;empty&gt;";
+
+	QString text = format ("<center><b>%1</b></center>", shortcut);
+	lb_output->setText (text);
+}
+
+// =============================================================================
+// =============================================================================
+void KeySequenceDialog::keyPressEvent (QKeyEvent* ev)
+{
+	seq = ev->key() + ev->modifiers();
+	updateOutput();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/configDialog.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,119 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include "mainWindow.h"
+#include <QDialog>
+
+class Ui_ConfigUI;
+class QLabel;
+class QDoubleSpinBox;
+
+// =============================================================================
+class ShortcutListItem : public QListWidgetItem
+{
+	PROPERTY (public,	KeySequenceConfig*,	keyConfig,	setKeyConfig,	STOCK_WRITE)
+	PROPERTY (public,	QAction*,			action,		setAction,		STOCK_WRITE)
+
+	public:
+		explicit ShortcutListItem (QListWidget* view = null, int type = Type) :
+			QListWidgetItem (view, type) {}
+};
+
+// =============================================================================
+class ConfigDialog : public QDialog
+{
+	Q_OBJECT
+
+	public:
+		enum Tab
+		{
+			InterfaceTab,
+			ProfileTab,
+			ShortcutsTab,
+			QuickColorsTab,
+			GridsTab,
+			ExtProgsTab,
+			DownloadTab
+		};
+
+		explicit ConfigDialog (Tab deftab = InterfaceTab, QWidget* parent = null, Qt::WindowFlags f = 0);
+		virtual ~ConfigDialog();
+		float getGridValue (int i, int j) const;
+
+		QList<LDQuickColor> quickColors;
+		QDoubleSpinBox* dsb_gridData[3][4];
+
+	private:
+		Ui_ConfigUI* ui;
+		QLabel* lb_gridLabels[3];
+		QLabel* lb_gridIcons[3];
+		QList<QListWidgetItem*> quickColorItems;
+
+		void applySettings();
+		void addShortcut (KeySequenceConfig& cfg, QAction* act, int& i);
+		void setButtonBackground (QPushButton* button, QString value);
+		void pickColor (QString& conf, QPushButton* button);
+		void updateQuickColorList (LDQuickColor* sel = null);
+		void setShortcutText (ShortcutListItem* item);
+		int getItemRow (QListWidgetItem* item, QList<QListWidgetItem*>& haystack);
+		QString quickColorString();
+		QListWidgetItem* getSelectedQuickColor();
+		QList<ShortcutListItem*> getShortcutSelection();
+		void initGrids();
+		void initExtProgs();
+
+	private slots:
+		void slot_setGLBackground();
+		void slot_setGLForeground();
+		void slot_setGLSelectColor();
+		void slot_setShortcut();
+		void slot_resetShortcut();
+		void slot_clearShortcut();
+		void slot_setColor();
+		void slot_delColor();
+		void slot_addColorSeparator();
+		void slot_moveColor();
+		void slot_clearColors();
+		void slot_setExtProgPath();
+		void slot_findDownloadFolder();
+		void buttonClicked (QAbstractButton* button);
+		void selectPage (int row);
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class KeySequenceDialog : public QDialog
+{
+	Q_OBJECT
+
+	public:
+		explicit KeySequenceDialog (QKeySequence seq, QWidget* parent = null, Qt::WindowFlags f = 0);
+		static bool staticDialog (KeySequenceConfig* cfg, QWidget* parent = null);
+
+		QLabel* lb_output;
+		QDialogButtonBox* bbx_buttons;
+		QKeySequence seq;
+
+	private:
+		void updateOutput();
+
+	private slots:
+		virtual void keyPressEvent (QKeyEvent* ev) override;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/configuration.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,179 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  config.cxx: Configuration management. I don't like how unsafe QSettings
+ *  is so this implements a type-safer and identifer-safer wrapping system of
+ *  configuration variables. QSettings is used underlyingly, this is a matter
+ *  of interface.
+ */
+
+#include <errno.h>
+#include <QDir>
+#include <QTextStream>
+#include <QSettings>
+#include "main.h"
+#include "configuration.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "ldDocument.h"
+
+#ifdef _WIN32
+# define EXTENSION ".ini"
+#else
+# define EXTENSION ".cfg"
+#endif // _WIN32
+
+Config*							g_configPointers[MAX_CONFIG];
+static int						g_cfgPointerCursor = 0;
+static QMap<QString, Config*>	g_configsByName;
+static QList<Config*>			g_configs;
+
+// =============================================================================
+// Get the QSettings object.
+// =============================================================================
+static QSettings* getSettingsObject()
+{
+	QString path = qApp->applicationDirPath() + "/" UNIXNAME EXTENSION;
+	return new QSettings (path, QSettings::IniFormat);
+}
+
+Config::Config (QString name) :
+	m_name (name) {}
+
+// =============================================================================
+// Load the configuration from file
+// =============================================================================
+bool Config::load()
+{
+	QSettings* settings = getSettingsObject();
+	print ("config::load: Loading configuration file from %1\n", settings->fileName());
+
+	for (Config* cfg : g_configPointers)
+	{
+		if (!cfg)
+			break;
+
+		QVariant val = settings->value (cfg->name(), cfg->getDefaultAsVariant());
+		cfg->loadFromVariant (val);
+		g_configsByName[cfg->name()] = cfg;
+		g_configs << cfg;
+	}
+
+	settings->deleteLater();
+	return true;
+}
+
+// =============================================================================
+//
+// Save the configuration to disk
+//
+bool Config::save()
+{
+	QSettings* settings = getSettingsObject();
+
+	for (Config* cfg : g_configs)
+	{
+		if (!cfg->isDefault())
+			settings->setValue (cfg->name(), cfg->toVariant());
+		else
+			settings->remove (cfg->name());
+	}
+
+	settings->sync();
+	print ("Configuration saved to %1.\n", settings->fileName());
+	settings->deleteLater();
+	return true;
+}
+
+// =============================================================================
+// Reset configuration to defaults.
+// =============================================================================
+void Config::reset()
+{
+	for (Config* cfg : g_configs)
+		cfg->resetValue();
+}
+
+// =============================================================================
+// Where is the configuration file located at?
+// =============================================================================
+QString Config::filepath (QString file)
+{
+	return Config::dirpath() + DIRSLASH + file;
+}
+
+// =============================================================================
+// Directory of the configuration file.
+// =============================================================================
+QString Config::dirpath()
+{
+	QSettings* cfg = getSettingsObject();
+	return dirname (cfg->fileName());
+}
+
+// =============================================================================
+// We cannot just add config objects to a list or vector because that would rely
+// on the vector's c-tor being called before the configs' c-tors. With global
+// variables we cannot assume that, therefore we need to use a C-style array here.
+// =============================================================================
+void Config::addToArray (Config* ptr)
+{
+	if (g_cfgPointerCursor == 0)
+		memset (g_configPointers, 0, sizeof g_configPointers);
+
+	assert (g_cfgPointerCursor < MAX_CONFIG);
+	g_configPointers[g_cfgPointerCursor++] = ptr;
+}
+
+// =============================================================================
+// =============================================================================
+template<class T> T* getConfigByName (QString name, Config::Type type)
+{
+	auto it = g_configsByName.find (name);
+
+	if (it == g_configsByName.end())
+		return null;
+
+	Config* cfg = it.value();
+
+	if (cfg->getType() != type)
+	{
+		fprint (stderr, "type of %1 is %2, not %3\n", name, cfg->getType(), type);
+		abort();
+	}
+
+	return reinterpret_cast<T*> (cfg);
+}
+
+// =============================================================================
+// =============================================================================
+#undef IMPLEMENT_CONFIG
+
+#define IMPLEMENT_CONFIG(NAME)										\
+	NAME##Config* NAME##Config::getByName (QString name)			\
+	{																\
+		return getConfigByName<NAME##Config> (name, E##NAME##Type);	\
+	}
+
+IMPLEMENT_CONFIG (Int)
+IMPLEMENT_CONFIG (String)
+IMPLEMENT_CONFIG (Bool)
+IMPLEMENT_CONFIG (Float)
+IMPLEMENT_CONFIG (List)
+IMPLEMENT_CONFIG (KeySequence)
+IMPLEMENT_CONFIG (Vertex)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/configuration.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,194 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QString>
+#include <QVariant>
+#include <QKeySequence>
+#include "macros.h"
+#include "basics.h"
+
+class QSettings;
+
+#define MAX_INI_LINE 512
+#define MAX_CONFIG 512
+
+#define cfg(T, NAME, DEFAULT) \
+	Config::T##Type NAME; \
+	T##Config config_##NAME (&NAME, #NAME, DEFAULT);
+
+#define extern_cfg(T, NAME) extern Config::T##Type NAME;
+
+// =========================================================
+class Config
+{
+	PROPERTY (private, QString, name, setName, STOCK_WRITE)
+
+	public:
+		enum Type
+		{
+			EIntType,
+			EStringType,
+			EFloatType,
+			EBoolType,
+			EKeySequenceType,
+			EListType,
+			EVertexType,
+		};
+
+		using IntType			= int;
+		using StringType		= QString;
+		using FloatType			= float;
+		using BoolType			= bool;
+		using KeySequenceType	= QKeySequence;
+		using ListType			= QList<QVariant>;
+		using VertexType		= Vertex;
+
+		Config (QString name);
+
+		virtual QVariant	getDefaultAsVariant() const = 0;
+		virtual Type		getType() const = 0;
+		virtual bool		isDefault() const = 0;
+		virtual void		loadFromVariant (const QVariant& val) = 0;
+		virtual void		resetValue() = 0;
+		virtual QVariant	toVariant() const = 0;
+
+		// ------------------------------------------
+		static bool load();
+		static bool save();
+		static void reset();
+		static QString dirpath();
+		static QString filepath (QString file);
+
+	protected:
+		static void addToArray (Config* ptr);
+};
+
+// =============================================================================
+#define IMPLEMENT_CONFIG(NAME)													\
+public:																			\
+	using ValueType = Config::NAME##Type;										\
+																				\
+	NAME##Config (ValueType* valueptr, QString name, ValueType def) :			\
+		Config (name),															\
+		m_valueptr (valueptr),													\
+		m_default (def)															\
+	{																			\
+		Config::addToArray (this);												\
+		*m_valueptr = def;														\
+	}																			\
+																				\
+	inline ValueType getValue() const											\
+	{																			\
+		return *m_valueptr;														\
+	}																			\
+																				\
+	inline void setValue (ValueType val)										\
+	{																			\
+		*m_valueptr = val;														\
+	}																			\
+																				\
+	virtual Config::Type getType() const										\
+	{																			\
+		return Config::E##NAME##Type;											\
+	}																			\
+																				\
+	virtual void resetValue()													\
+	{																			\
+		*m_valueptr = m_default;												\
+	}																			\
+																				\
+	virtual const ValueType& getDefault() const									\
+	{																			\
+		return m_default;														\
+	}																			\
+																				\
+	virtual bool isDefault() const												\
+	{																			\
+		return *m_valueptr == m_default;										\
+	}																			\
+																				\
+	virtual void loadFromVariant (const QVariant& val)							\
+	{																			\
+		*m_valueptr = val.value<ValueType>();									\
+	}																			\
+																				\
+	virtual QVariant toVariant() const											\
+	{																			\
+		return QVariant::fromValue<ValueType> (*m_valueptr);					\
+	}																			\
+																				\
+	virtual QVariant getDefaultAsVariant() const								\
+	{																			\
+		return QVariant::fromValue<ValueType> (m_default);						\
+	}																			\
+																				\
+	static NAME##Config* getByName (QString name);								\
+																				\
+private:																		\
+	ValueType*	m_valueptr;														\
+	ValueType	m_default;
+
+// =============================================================================
+//
+class IntConfig : public Config
+{
+	IMPLEMENT_CONFIG (Int)
+};
+
+// =============================================================================
+//
+class StringConfig : public Config
+{
+	IMPLEMENT_CONFIG (String)
+};
+
+// =============================================================================
+//
+class FloatConfig : public Config
+{
+	IMPLEMENT_CONFIG (Float)
+};
+
+// =============================================================================
+//
+class BoolConfig : public Config
+{
+	IMPLEMENT_CONFIG (Bool)
+};
+
+// =============================================================================
+//
+class KeySequenceConfig : public Config
+{
+	IMPLEMENT_CONFIG (KeySequence)
+};
+
+// =============================================================================
+//
+class ListConfig : public Config
+{
+	IMPLEMENT_CONFIG (List)
+};
+
+// =============================================================================
+//
+class VertexConfig : public Config
+{
+	IMPLEMENT_CONFIG (Vertex)
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/crashCatcher.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,139 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef __unix__
+
+#include <QString>
+#include <QProcess>
+#include <QTemporaryFile>
+#include <QMessageBox>
+#include <unistd.h>
+#include <signal.h>
+
+#ifdef Q_OS_LINUX
+# include <sys/prctl.h>
+#endif
+
+#include "crashCatcher.h"
+#include "basics.h"
+#include "dialogs.h"
+
+// Is the crash catcher active now?
+static bool g_crashCatcherActive = false;
+
+// If an assertion failed, what was it?
+static QString g_assertionFailure;
+
+// List of signals to catch and crash on
+static QList<int> g_signalsToCatch ({
+	SIGSEGV, // segmentation fault
+	SIGABRT, // abort() calls
+	SIGFPE, // floating point exceptions (e.g. division by zero)
+	SIGILL, // illegal instructions
+});
+
+// =============================================================================
+//
+static void handleCrash (int sig)
+{
+	printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig);
+
+	if (g_crashCatcherActive)
+	{
+		printf ("caught signal while crash catcher is active!\n");
+		exit (149);
+	}
+
+	const pid_t pid = getpid();
+	QProcess proc;
+	QTemporaryFile commandsFile;
+
+	g_crashCatcherActive = true;
+
+	if (commandsFile.open())
+	{
+		commandsFile.write (format ("attach %1\n", pid).toLocal8Bit());
+		commandsFile.write (QString ("backtrace full\n").toLocal8Bit());
+		commandsFile.write (QString ("detach\n").toLocal8Bit());
+		commandsFile.write (QString ("quit").toLocal8Bit());
+		commandsFile.flush();
+		commandsFile.close();
+	}
+
+	QStringList args ({"-x", commandsFile.fileName()});
+
+	proc.start ("gdb", args);
+
+	// Linux doesn't allow ptrace to be used on anything but direct child processes
+	// so we need to use prctl to register an exception to this to allow GDB attach to us.
+	// We need to do this now and no earlier because only now we actually know GDB's PID.
+#ifdef Q_OS_LINUX
+	prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0);
+#endif
+
+	proc.waitForFinished (1000);
+	QString output = QString (proc.readAllStandardOutput());
+	QString err = QString (proc.readAllStandardError());
+
+	bombBox (format ("<h3>Program crashed with signal %1</h3>\n\n"
+		"%2"
+		"<p><b>GDB <tt>stdout</tt>:</b></p><pre>%3</pre>\n"
+		"<p><b>GDB <tt>stderr</tt>:</b></p><pre>%4</pre>",
+		sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err));
+}
+
+// =============================================================================
+//
+void initCrashCatcher()
+{
+	struct sigaction sighandler;
+	sighandler.sa_handler = &handleCrash;
+	sighandler.sa_flags = 0;
+	sigemptyset (&sighandler.sa_mask);
+
+	for (int sig : g_signalsToCatch)
+		sigaction (sig, &sighandler, null);
+
+	print ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch);
+}
+#endif // #ifdef __unix__
+
+// =============================================================================
+//
+// This function must be readily available in both Windows and Linux. We display
+// the bomb box straight in Windows while in Linux we let abort() trigger the
+// signal handler, which will cause the usual bomb box with GDB diagnostics.
+// Said prompt will embed the assertion failure information.
+//
+void assertionFailure (const char* file, int line, const char* funcname, const char* expr)
+{
+	QString errmsg = format (
+		"<p><b>File</b>: <tt>%1</tt><br />"
+		"<b>Line</b>: <tt>%2</tt><br />"
+		"<b>Function:</b> <tt>%3</tt></p>"
+		"<p>Assertion <b><tt>`%4'</tt></b> failed.</p>",
+		file, line, funcname, expr);
+
+	g_assertionFailure = errmsg;
+
+#ifndef __unix__
+	bombBox (errmsg);
+#endif
+
+	abort();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/crashCatcher.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,24 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#ifdef __unix__
+void initCrashCatcher();
+#else // ifdef __unix__
+# define initCrashCatcher()
+#endif // ifdef __unix__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dialogs.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,378 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDialog>
+#include <QLineEdit>
+#include <QSpinBox>
+#include <QDialogButtonBox>
+#include <QFileDialog>
+#include <QLabel>
+#include <QPushButton>
+#include <QBoxLayout>
+#include <QGridLayout>
+#include <QProgressBar>
+#include <QCheckBox>
+#include <QDesktopServices>
+#include <QMessageBox>
+#include <QUrl>
+#include "dialogs.h"
+#include "radioGroup.h"
+#include "mainWindow.h"
+#include "glRenderer.h"
+#include "documentation.h"
+#include "ldDocument.h"
+#include "dialogs.h"
+#include "ui_overlay.h"
+#include "ui_ldrawpath.h"
+#include "ui_openprogress.h"
+#include "ui_extprogpath.h"
+#include "ui_about.h"
+#include "ui_bombbox.h"
+
+extern const char* g_extProgPathFilter;
+extern_cfg (String, io_ldpath);
+
+// =============================================================================
+// =============================================================================
+OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f)
+{
+	ui = new Ui_OverlayUI;
+	ui->setupUi (this);
+
+	m_cameraArgs =
+	{
+		{ ui->top,    GL::ETopCamera },
+		{ ui->bottom, GL::EBottomCamera },
+		{ ui->front,  GL::EFrontCamera },
+		{ ui->back,   GL::EBackCamera },
+		{ ui->left,   GL::ELeftCamera },
+		{ ui->right,  GL::ERightCamera }
+	};
+
+	GL::EFixedCamera cam = g_win->R()->camera();
+
+	if (cam == GL::EFreeCamera)
+		cam = GL::ETopCamera;
+
+	connect (ui->width, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged()));
+	connect (ui->height, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged()));
+	connect (ui->buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_help()));
+	connect (ui->fileSearchButton, SIGNAL (clicked (bool)), this, SLOT (slot_fpath()));
+
+	slot_dimensionsChanged();
+	fillDefaults (cam);
+}
+
+// =============================================================================
+// =============================================================================
+OverlayDialog::~OverlayDialog()
+{
+	delete ui;
+}
+
+// =============================================================================
+// =============================================================================
+void OverlayDialog::fillDefaults (int newcam)
+{
+	LDGLOverlay& info = g_win->R()->getOverlay (newcam);
+	radioDefault<int> (newcam, m_cameraArgs);
+
+	if (info.img != null)
+	{
+		ui->filename->setText (info.fname);
+		ui->originX->setValue (info.ox);
+		ui->originY->setValue (info.oy);
+		ui->width->setValue (info.lw);
+		ui->height->setValue (info.lh);
+	}
+	else
+	{
+		ui->filename->setText ("");
+		ui->originX->setValue (0);
+		ui->originY->setValue (0);
+		ui->width->setValue (0.0f);
+		ui->height->setValue (0.0f);
+	}
+}
+
+// =============================================================================
+// =============================================================================
+QString OverlayDialog::fpath() const
+{
+	return ui->filename->text();
+}
+
+int OverlayDialog::ofsx() const
+{
+	return ui->originX->value();
+}
+
+int OverlayDialog::ofsy() const
+{
+	return ui->originY->value();
+}
+
+double OverlayDialog::lwidth() const
+{
+	return ui->width->value();
+}
+
+double OverlayDialog::lheight() const
+{
+	return ui->height->value();
+}
+
+int OverlayDialog::camera() const
+{
+	return radioSwitch<int> (GL::ETopCamera, m_cameraArgs);
+}
+
+void OverlayDialog::slot_fpath()
+{
+	ui->filename->setText (QFileDialog::getOpenFileName (null, "Overlay image"));
+}
+
+void OverlayDialog::slot_help()
+{
+	showDocumentation (g_docs_overlays);
+}
+
+void OverlayDialog::slot_dimensionsChanged()
+{
+	bool enable = (ui->width->value() != 0) || (ui->height->value() != 0);
+	ui->buttonBox->button (QDialogButtonBox::Ok)->setEnabled (enable);
+}
+
+// =============================================================================
+// =============================================================================
+LDrawPathDialog::LDrawPathDialog (const bool validDefault, QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f),
+	m_validDefault (validDefault)
+{
+	ui = new Ui_LDPathUI;
+	ui->setupUi (this);
+	ui->status->setText ("---");
+
+	if (validDefault)
+		ui->heading->hide();
+	else
+	{
+		cancelButton()->setText ("Exit");
+		cancelButton()->setIcon (getIcon ("exit"));
+	}
+
+	okButton()->setEnabled (false);
+
+	connect (ui->path, SIGNAL (textEdited (QString)), this, SLOT (slot_tryConfigure()));
+	connect (ui->searchButton, SIGNAL (clicked()), this, SLOT (slot_findPath()));
+	connect (ui->buttonBox, SIGNAL (rejected()), this, validDefault ? SLOT (reject()) : SLOT (slot_exit()));
+	connect (ui->buttonBox, SIGNAL (accepted()), this, SLOT (slot_accept()));
+
+	setPath (io_ldpath);
+
+	if (validDefault)
+		slot_tryConfigure();
+}
+
+// =============================================================================
+// =============================================================================
+LDrawPathDialog::~LDrawPathDialog()
+{
+	delete ui;
+}
+
+QPushButton* LDrawPathDialog::okButton()
+{
+	return ui->buttonBox->button (QDialogButtonBox::Ok);
+}
+
+QPushButton* LDrawPathDialog::cancelButton()
+{
+	return ui->buttonBox->button (QDialogButtonBox::Cancel);
+}
+
+void LDrawPathDialog::setPath (QString path)
+{
+	ui->path->setText (path);
+}
+
+QString LDrawPathDialog::filename() const
+{
+	return ui->path->text();
+}
+
+// =============================================================================
+// =============================================================================
+void LDrawPathDialog::slot_findPath()
+{
+	QString newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path");
+
+	if (newpath.length() > 0 && newpath != filename())
+	{
+		setPath (newpath);
+		slot_tryConfigure();
+	}
+}
+
+// =============================================================================
+// =============================================================================
+void LDrawPathDialog::slot_exit()
+{
+	exit (0);
+}
+
+// =============================================================================
+// =============================================================================
+void LDrawPathDialog::slot_tryConfigure()
+{
+	if (LDPaths::tryConfigure (filename()) == false)
+	{
+		ui->status->setText (format ("<span style=\"color:#700; \">%1</span>", LDPaths::getError()));
+		okButton()->setEnabled (false);
+		return;
+	}
+
+	ui->status->setText ("<span style=\"color: #270; \">OK!</span>");
+	okButton()->setEnabled (true);
+}
+
+// =============================================================================
+// =============================================================================
+void LDrawPathDialog::slot_accept()
+{
+	Config::save();
+	accept();
+}
+
+// =============================================================================
+// =============================================================================
+OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f)
+{
+	ui = new Ui_OpenProgressUI;
+	ui->setupUi (this);
+	ui->progressText->setText ("Parsing...");
+	setNumLines (0);
+	m_progress = 0;
+}
+
+// =============================================================================
+// =============================================================================
+OpenProgressDialog::~OpenProgressDialog()
+{
+	delete ui;
+}
+
+// =============================================================================
+// =============================================================================
+void OpenProgressDialog::setNumLines (int const& a)
+{
+	m_numLines = a;
+	ui->progressBar->setRange (0, numLines());
+	updateValues();
+}
+
+// =============================================================================
+// =============================================================================
+void OpenProgressDialog::updateValues()
+{
+	ui->progressText->setText (format ("Parsing... %1 / %2", progress(), numLines()));
+	ui->progressBar->setValue (progress());
+}
+
+// =============================================================================
+// =============================================================================
+void OpenProgressDialog::updateProgress (int progress)
+{
+	setProgress (progress);
+	updateValues();
+}
+
+// =============================================================================
+// =============================================================================
+ExtProgPathPrompt::ExtProgPathPrompt (QString progName, QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f),
+	ui (new Ui_ExtProgPath)
+{
+	ui->setupUi (this);
+	QString labelText = ui->m_label->text();
+	labelText.replace ("<PROGRAM>", progName);
+	ui->m_label->setText (labelText);
+	connect (ui->m_findPath, SIGNAL (clicked (bool)), this, SLOT (findPath()));
+}
+
+// =============================================================================
+// =============================================================================
+ExtProgPathPrompt::~ExtProgPathPrompt()
+{
+	delete ui;
+}
+
+// =============================================================================
+// =============================================================================
+void ExtProgPathPrompt::findPath()
+{
+	QString path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter);
+
+	if (!path.isEmpty())
+		ui->m_path->setText (path);
+}
+
+// =============================================================================
+// =============================================================================
+QString ExtProgPathPrompt::getPath() const
+{
+	return ui->m_path->text();
+}
+
+// =============================================================================
+// =============================================================================
+AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f)
+{
+	Ui::AboutUI ui;
+	ui.setupUi (this);
+	ui.versionInfo->setText (APPNAME " " + QString (fullVersionString()));
+
+	QPushButton* mailButton = new QPushButton;
+	mailButton->setText (tr ("Contact"));
+	mailButton->setIcon (getIcon ("mail"));
+	ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole);
+	connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail()));
+
+	setWindowTitle (format (tr ("About %1"), APPNAME));
+}
+
+// =============================================================================
+// =============================================================================
+void AboutDialog::slot_mail()
+{
+	QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <arezey@gmail.com>?subject=LDForge"));
+}
+
+// =============================================================================
+// =============================================================================
+void bombBox (const QString& message)
+{
+	QDialog dlg (g_win);
+	Ui_BombBox ui;
+
+	ui.setupUi (&dlg);
+	ui.m_text->setText (message);
+	ui.buttonBox->button (QDialogButtonBox::Close)->setText (QObject::tr ("Damn it"));
+	dlg.exec();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dialogs.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,141 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QDialog>
+#include "main.h"
+#include "basics.h"
+
+class Ui_ExtProgPath;
+class QRadioButton;
+class QCheckBox;
+class QProgressBar;
+class QGroupBox;
+class QDialogButtonBox;
+class QDoubleSpinBox;
+class QPushButton;
+class QLineEdit;
+class QSpinBox;
+class RadioGroup;
+class QLabel;
+class QAbstractButton;
+class Ui_OverlayUI;
+class Ui_LDPathUI;
+class Ui_OpenProgressUI;
+
+class OverlayDialog : public QDialog
+{
+	Q_OBJECT
+
+	public:
+		explicit OverlayDialog (QWidget* parent = null, Qt::WindowFlags f = 0);
+		virtual ~OverlayDialog();
+
+		QString         fpath() const;
+		int         ofsx() const;
+		int         ofsy() const;
+		double      lwidth() const;
+		double      lheight() const;
+		int         camera() const;
+
+	private:
+		Ui_OverlayUI* ui;
+		QList<Pair<QRadioButton*, int>> m_cameraArgs;
+
+	private slots:
+		void slot_fpath();
+		void slot_help();
+		void slot_dimensionsChanged();
+		void fillDefaults (int newcam);
+};
+
+// =============================================================================
+class LDrawPathDialog : public QDialog
+{
+	Q_OBJECT
+
+	public:
+		explicit LDrawPathDialog (const bool validDefault, QWidget* parent = null, Qt::WindowFlags f = 0);
+		virtual ~LDrawPathDialog();
+		QString filename() const;
+		void setPath (QString path);
+
+	private:
+		Q_DISABLE_COPY (LDrawPathDialog)
+		const bool m_validDefault;
+		Ui_LDPathUI* ui;
+		QPushButton* okButton();
+		QPushButton* cancelButton();
+
+	private slots:
+		void slot_findPath();
+		void slot_tryConfigure();
+		void slot_exit();
+		void slot_accept();
+};
+
+// =============================================================================
+class OpenProgressDialog : public QDialog
+{
+	Q_OBJECT
+	PROPERTY (public,	int, progress,	setProgress,	STOCK_WRITE)
+	PROPERTY (public,	int, numLines,	setNumLines,	CUSTOM_WRITE)
+
+	public:
+		explicit OpenProgressDialog (QWidget* parent = null, Qt::WindowFlags f = 0);
+		virtual ~OpenProgressDialog();
+
+	public slots:
+		void updateProgress (int progress);
+
+	private:
+		Ui_OpenProgressUI* ui;
+
+		void updateValues();
+};
+
+// =============================================================================
+class ExtProgPathPrompt : public QDialog
+{
+	Q_OBJECT
+
+	public:
+		explicit ExtProgPathPrompt (QString progName, QWidget* parent = 0, Qt::WindowFlags f = 0);
+		virtual ~ExtProgPathPrompt();
+		QString getPath() const;
+
+	public slots:
+		void findPath();
+
+	private:
+		Ui_ExtProgPath* ui;
+};
+
+// =============================================================================
+class AboutDialog : public QDialog
+{
+	Q_OBJECT
+
+	public:
+		AboutDialog (QWidget* parent = null, Qt::WindowFlags f = 0);
+
+	private slots:
+		void slot_mail();
+};
+
+void bombBox (const QString& message);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/documentation.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,76 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDialog>
+#include <QTextEdit>
+#include <QDialogButtonBox>
+#include <QBoxLayout>
+#include "main.h"
+#include "basics.h"
+
+// =============================================================================
+// =============================================================================
+class DocumentViewer : public QDialog
+{
+	public:
+		explicit DocumentViewer (QWidget* parent = null, Qt::WindowFlags f = 0) : QDialog (parent, f)
+		{
+			te_text = new QTextEdit (this);
+			te_text->setMinimumSize (QSize (400, 300));
+			te_text->setReadOnly (true);
+
+			QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Close);
+			QVBoxLayout* layout = new QVBoxLayout (this);
+			layout->addWidget (te_text);
+			layout->addWidget (bbx_buttons);
+
+			connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject()));
+		}
+
+		void setText (const char* text)
+		{
+			te_text->setText (text);
+		}
+
+	private:
+		QTextEdit* te_text;
+};
+
+const char* g_docs_overlays =
+	"<h1>Overlay images</h1><br />"
+	"<p>" APPNAME " supports drawing transparent images over the part model. This "
+	"can be used to have, for instance, a photo of the part overlaid on top of the "
+	"model and use it for drawing curves somewhat accurately.</p>"
+	"<p>For this purpose, a specific photo has to be taken of the part; it should "
+	"represent the part as true as possible to the actual camera used for editing. "
+	"The image should be taken from straight above the part, at as an orthogonal "
+	"angle as possible. It is recommended to take a lot of pictures this way and "
+	"select the best candidate.</p>"
+	"<p>The image should then be cropped with the knowledge of the image's LDU "
+	"dimensions in mind. The offset should then be identified in the image in pixels.</p>"
+	"<p>Finally, use the \"Set Overlay Image\" dialog and fill in the details. The "
+	"overlay image should then be ready for use.";
+
+// =============================================================================
+// =============================================================================
+void showDocumentation (const char* text)
+{
+	DocumentViewer dlg;
+	dlg.setText (text);
+	dlg.exec();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/documentation.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,21 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+extern const char* g_docs_overlays;
+void showDocumentation (const char* text);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editHistory.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,202 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "editHistory.h"
+#include "ldObject.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "glRenderer.h"
+
+// =============================================================================
+//
+History::History() :
+	m_position (-1) {}
+
+// =============================================================================
+//
+void History::undo()
+{
+	if (m_changesets.isEmpty() || position() == -1)
+		return;
+
+	// Don't take the changes done here as actual edits to the document
+	setIgnoring (true);
+
+	const Changeset& set = getChangeset (position());
+
+	// Iterate the list in reverse and undo all actions
+	for (int i = set.size() - 1; i >= 0; --i)
+	{
+		AbstractHistoryEntry* change = set[i];
+		change->undo();
+	}
+
+	m_position--;
+	g_win->refresh();
+	g_win->updateActions();
+	dprint ("Position is now %1", position());
+	setIgnoring (false);
+}
+
+// =============================================================================
+//
+void History::redo()
+{
+	if (position() == m_changesets.size())
+		return;
+
+	setIgnoring (true);
+	const Changeset& set = getChangeset (position() + 1);
+
+	// Redo things - in the order as they were done in the first place
+	for (const AbstractHistoryEntry* change : set)
+		change->redo();
+
+	setPosition (position() + 1);
+	g_win->refresh();
+	g_win->updateActions();
+	dprint ("Position is now %1", position());
+	setIgnoring (false);
+}
+
+// =============================================================================
+//
+void History::clear()
+{
+	for (Changeset set : m_changesets)
+		for (AbstractHistoryEntry* change : set)
+			delete change;
+
+	m_changesets.clear();
+	dprint ("History: cleared");
+}
+
+// =============================================================================
+//
+void History::addStep()
+{
+	if (m_currentChangeset.isEmpty())
+		return;
+
+	while (position() < getSize() - 1)
+	{
+		Changeset last = m_changesets.last();
+
+		for (AbstractHistoryEntry* entry : last)
+			delete entry;
+
+		m_changesets.removeLast();
+	}
+
+	dprint ("History: step added (%1 changes)", m_currentChangeset.size());
+	m_changesets << m_currentChangeset;
+	m_currentChangeset.clear();
+	setPosition (position() + 1);
+	g_win->updateActions();
+}
+
+// =============================================================================
+//
+void History::add (AbstractHistoryEntry* entry)
+{
+	if (isIgnoring())
+	{
+		delete entry;
+		return;
+	}
+
+	entry->setParent (this);
+	m_currentChangeset << entry;
+	dprint ("History: added entry of type %1", entry->getTypeName());
+}
+
+// =============================================================================
+//
+void AddHistory::undo() const
+{
+	LDObject* obj = parent()->document()->getObject (index());
+	obj->destroy();
+}
+
+// =============================================================================
+//
+void AddHistory::redo() const
+{
+	LDObject* obj = parseLine (code());
+	parent()->document()->insertObj (index(), obj);
+	g_win->R()->compileObject (obj);
+}
+
+// =============================================================================
+//
+DelHistory::DelHistory (int idx, LDObject* obj) :
+	m_index (idx),
+	m_code (obj->asText()) {}
+
+// =============================================================================
+// heh
+//
+void DelHistory::undo() const
+{
+	LDObject* obj = parseLine (code());
+	parent()->document()->insertObj (index(), obj);
+	g_win->R()->compileObject (obj);
+}
+
+// =============================================================================
+//
+void DelHistory::redo() const
+{
+	LDObject* obj = parent()->document()->getObject (index());
+	obj->destroy();
+}
+
+// =============================================================================
+//
+void EditHistory::undo() const
+{
+	LDObject* obj = getCurrentDocument()->getObject (index());
+	LDObject* newobj = parseLine (oldCode());
+	obj->replace (newobj);
+	g_win->R()->compileObject (newobj);
+}
+
+// =============================================================================
+//
+void EditHistory::redo() const
+{
+	LDObject* obj = getCurrentDocument()->getObject (index());
+	LDObject* newobj = parseLine (newCode());
+	obj->replace (newobj);
+	g_win->R()->compileObject (newobj);
+}
+
+// =============================================================================
+//
+void SwapHistory::undo() const
+{
+	LDObject::fromID (a)->swap (LDObject::fromID (b));
+}
+
+// =============================================================================
+//
+void SwapHistory::redo() const
+{
+	undo();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editHistory.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,175 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include "main.h"
+#include "ldObject.h"
+
+#define IMPLEMENT_HISTORY_TYPE(N)							\
+	virtual ~N##History() {}								\
+	virtual void undo() const override;						\
+	virtual void redo() const override;						\
+															\
+	virtual History::EHistoryType getType() const override	\
+	{														\
+		return History::E##N##History;						\
+	}														\
+															\
+	virtual QString getTypeName() const						\
+	{														\
+		return #N;											\
+	}
+
+class AbstractHistoryEntry;
+
+// =============================================================================
+class History
+{
+	PROPERTY (private,	int,			position,	setPosition,	STOCK_WRITE)
+	PROPERTY (public,	LDDocument*,	document,	setDocument,	STOCK_WRITE)
+	PROPERTY (public,	bool,			isIgnoring,	setIgnoring,	STOCK_WRITE)
+
+	public:
+		typedef QList<AbstractHistoryEntry*> Changeset;
+
+		enum EHistoryType
+		{
+			EDelHistory,
+			EEditHistory,
+			EAddHistory,
+			EMoveHistory,
+			ESwapHistory,
+		};
+
+		History();
+		void undo();
+		void redo();
+		void clear();
+
+		void addStep();
+		void add (AbstractHistoryEntry* entry);
+
+		inline long getSize() const
+		{
+			return m_changesets.size();
+		}
+
+		inline History& operator<< (AbstractHistoryEntry* entry)
+		{
+			add (entry);
+			return *this;
+		}
+
+		inline const Changeset& getChangeset (long pos) const
+		{
+			return m_changesets[pos];
+		}
+
+	private:
+		Changeset m_currentChangeset;
+		QList<Changeset> m_changesets;
+};
+
+// =============================================================================
+//
+class AbstractHistoryEntry
+{
+	PROPERTY (public,	History*,	parent,	setParent,	STOCK_WRITE)
+
+	public:
+		virtual ~AbstractHistoryEntry() {}
+		virtual void undo() const = 0;
+		virtual void redo() const = 0;
+		virtual History::EHistoryType getType() const = 0;
+		virtual QString getTypeName() const = 0;
+};
+
+// =============================================================================
+//
+class DelHistory : public AbstractHistoryEntry
+{
+	PROPERTY (private,	int,		index,	setIndex,	STOCK_WRITE)
+	PROPERTY (private,	QString,	code,	setCode,	STOCK_WRITE)
+
+	public:
+		IMPLEMENT_HISTORY_TYPE (Del)
+		DelHistory (int idx, LDObject* obj);
+};
+
+// =============================================================================
+//
+class EditHistory : public AbstractHistoryEntry
+{
+	PROPERTY (private,	int, 		index,		setIndex,	STOCK_WRITE)
+	PROPERTY (private,	QString,	oldCode,	setOldCode,	STOCK_WRITE)
+	PROPERTY (private,	QString,	newCode,	setNewCode,	STOCK_WRITE)
+
+	public:
+		IMPLEMENT_HISTORY_TYPE (Edit)
+
+		EditHistory (int idx, QString oldCode, QString newCode) :
+			m_index (idx),
+			m_oldCode (oldCode),
+			m_newCode (newCode) {}
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class AddHistory : public AbstractHistoryEntry
+{
+	PROPERTY (private,	int,		index,	setIndex,	STOCK_WRITE)
+	PROPERTY (private,	QString,	code,	setCode,	STOCK_WRITE)
+
+	public:
+		IMPLEMENT_HISTORY_TYPE (Add)
+
+		AddHistory (int idx, LDObject* obj) :
+			m_index (idx),
+			m_code (obj->asText()) {}
+};
+
+// =============================================================================
+//
+class MoveHistory : public AbstractHistoryEntry
+{
+	public:
+		IMPLEMENT_HISTORY_TYPE (Move)
+
+		QList<int> indices;
+		Vertex dest;
+
+		MoveHistory (QList<int> indices, Vertex dest) :
+				indices (indices),
+				dest (dest) {}
+};
+
+// =============================================================================
+//
+class SwapHistory : public AbstractHistoryEntry
+{
+	public:
+		IMPLEMENT_HISTORY_TYPE (Swap)
+
+		SwapHistory (int a, int b) :
+			a (a),
+			b (b) {}
+
+	private:
+		int a, b;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/extPrograms.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,696 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QProcess>
+#include <QTemporaryFile>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QSpinBox>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QGridLayout>
+#include "main.h"
+#include "configuration.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "radioGroup.h"
+#include "editHistory.h"
+#include "ui_ytruder.h"
+#include "ui_intersector.h"
+#include "ui_rectifier.h"
+#include "ui_coverer.h"
+#include "ui_isecalc.h"
+#include "ui_edger2.h"
+#include "dialogs.h"
+
+enum extprog
+{
+	Isecalc,
+	Intersector,
+	Coverer,
+	Ytruder,
+	Rectifier,
+	Edger2,
+};
+
+// =============================================================================
+//
+cfg (String, prog_isecalc, "");
+cfg (String, prog_intersector, "");
+cfg (String, prog_coverer, "");
+cfg (String, prog_ytruder, "");
+cfg (String, prog_rectifier, "");
+cfg (String, prog_edger2, "");
+
+QString* const g_extProgPaths[] =
+{
+	&prog_isecalc,
+	&prog_intersector,
+	&prog_coverer,
+	&prog_ytruder,
+	&prog_rectifier,
+	&prog_edger2,
+};
+
+#ifndef _WIN32
+cfg (Bool, prog_isecalc_wine, false);
+cfg (Bool, prog_intersector_wine, false);
+cfg (Bool, prog_coverer_wine, false);
+cfg (Bool, prog_ytruder_wine, false);
+cfg (Bool, prog_rectifier_wine, false);
+cfg (Bool, prog_edger2_wine, false);
+
+bool* const g_extProgWine[] =
+{
+	&prog_isecalc_wine,
+	&prog_intersector_wine,
+	&prog_coverer_wine,
+	&prog_ytruder_wine,
+	&prog_rectifier_wine,
+	&prog_edger2_wine,
+};
+#endif // _WIN32
+
+const char* g_extProgNames[] =
+{
+	"Isecalc",
+	"Intersector",
+	"Coverer",
+	"Ytruder",
+	"Rectifier",
+	"Edger2"
+};
+
+// =============================================================================
+//
+static bool mkTempFile (QTemporaryFile& tmp, QString& fname)
+{
+	if (!tmp.open())
+		return false;
+
+	fname = tmp.fileName();
+	tmp.close();
+	return true;
+}
+
+// =============================================================================
+//
+static bool checkProgPath (const extprog prog)
+{
+	QString& path = *g_extProgPaths[prog];
+
+	if (path.length() > 0)
+		return true;
+
+	ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]);
+
+	if (dlg->exec() && !dlg->getPath().isEmpty())
+	{
+		path = dlg->getPath();
+		return true;
+	}
+
+	return false;
+}
+
+// =============================================================================
+//
+static QString processErrorString (extprog prog, QProcess& proc)
+{
+	switch (proc.error())
+	{
+		case QProcess::FailedToStart:
+		{
+			QString wineblurb;
+
+#ifndef _WIN32
+			if (*g_extProgWine[prog])
+				wineblurb = "make sure Wine is installed and ";
+#endif
+
+			return format ("Program failed to start, %1check your permissions", wineblurb);
+		} break;
+
+		case QProcess::Crashed:
+			return "Crashed.";
+
+		case QProcess::WriteError:
+		case QProcess::ReadError:
+			return "I/O error.";
+
+		case QProcess::UnknownError:
+			return "Unknown error";
+
+		case QProcess::Timedout:
+			return format ("Timed out (30 seconds)");
+	}
+
+	return "";
+}
+
+// =============================================================================
+//
+static void writeObjects (const LDObjectList& objects, QFile& f)
+{
+	for (LDObject* obj : objects)
+	{
+		if (obj->type() == LDObject::ESubfile)
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			LDObjectList objs = ref->inlineContents (LDSubfile::DeepInline);
+
+			writeObjects (objs, f);
+
+			for (LDObject* obj : objs)
+				obj->destroy();
+		}
+		else
+			f.write ((obj->asText() + "\r\n").toUtf8());
+	}
+}
+
+// =============================================================================
+//
+static void writeObjects (const LDObjectList& objects, QString fname)
+{
+	// Write the input file
+	QFile f (fname);
+
+	if (!f.open (QIODevice::WriteOnly | QIODevice::Text))
+	{
+		critical (format ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString()));
+		return;
+	}
+
+	writeObjects (objects, f);
+	f.close();
+
+#ifdef DEBUG
+	QFile::copy (fname, "debug_lastInput");
+#endif
+}
+
+// =============================================================================
+//
+void writeSelection (QString fname)
+{
+	writeObjects (selection(), fname);
+}
+
+// =============================================================================
+//
+void writeColorGroup (const int colnum, QString fname)
+{
+	LDObjectList objects;
+
+	for (LDObject* obj : getCurrentDocument()->objects())
+	{
+		if (obj->isColored() == false || obj->color() != colnum)
+			continue;
+
+		objects << obj;
+	}
+
+	writeObjects (objects, fname);
+}
+
+// =============================================================================
+//
+bool runUtilityProcess (extprog prog, QString path, QString argvstr)
+{
+	QTemporaryFile input;
+	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
+
+#ifndef _WIN32
+	if (*g_extProgWine[prog])
+	{
+		argv.insert (0, path);
+		path = "wine";
+	}
+#endif // _WIN32
+
+	print ("Running command: %1 %2\n", path, argv.join (" "));
+
+	if (!input.open())
+		return false;
+
+	QProcess proc;
+
+	// Begin!
+	proc.setStandardInputFile (input.fileName());
+	proc.start (path, argv);
+
+	if (!proc.waitForStarted())
+	{
+		critical (format ("Couldn't start %1: %2\n", g_extProgNames[prog], processErrorString (prog, proc)));
+		return false;
+	}
+
+	// Write an enter, the utility tools all expect one
+	input.write ("\n");
+
+	// Wait while it runs
+	proc.waitForFinished();
+
+	QString err = "";
+
+	if (proc.exitStatus() != QProcess::NormalExit)
+		err = processErrorString (prog, proc);
+
+	// Check the return code
+	if (proc.exitCode() != 0)
+		err = format ("Program exited abnormally (return code %1).",  proc.exitCode());
+
+	if (!err.isEmpty())
+	{
+		critical (format ("%1 failed: %2\n", g_extProgNames[prog], err));
+		return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+static void insertOutput (QString fname, bool replace, QList<int> colorsToReplace)
+{
+#ifdef DEBUG
+	QFile::copy (fname, "./debug_lastOutput");
+#endif // RELEASE
+
+	// Read the output file
+	QFile f (fname);
+
+	if (!f.open (QIODevice::ReadOnly))
+	{
+		critical (format ("Couldn't open temporary file %1 for reading.\n", fname));
+		return;
+	}
+
+	LDObjectList objs = loadFileContents (&f, null);
+
+	// If we replace the objects, delete the selection now.
+	if (replace)
+		g_win->deleteSelection();
+
+	for (int colnum : colorsToReplace)
+		g_win->deleteByColor (colnum);
+
+	// Insert the new objects
+	getCurrentDocument()->clearSelection();
+
+	for (LDObject* obj : objs)
+	{
+		if (!obj->isScemantic())
+		{
+			obj->destroy();
+			continue;
+		}
+
+		getCurrentDocument()->addObject (obj);
+		obj->select();
+	}
+
+	g_win->doFullRefresh();
+}
+
+// =============================================================================
+// Interface for Ytruder
+// =============================================================================
+DEFINE_ACTION (Ytruder, 0)
+{
+	setlocale (LC_ALL, "C");
+
+	if (!checkProgPath (Ytruder))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::YtruderUI ui;
+	ui.setupUi (dlg);
+
+	if (!dlg->exec())
+		return;
+
+	// Read the user's choices
+	const enum { Distance, Symmetry, Projection, Radial } mode =
+		ui.mode_distance->isChecked()   ? Distance :
+		ui.mode_symmetry->isChecked()   ? Symmetry :
+		ui.mode_projection->isChecked() ? Projection : Radial;
+
+	const Axis axis =
+		ui.axis_x->isChecked() ? X :
+		ui.axis_y->isChecked() ? Y : Z;
+
+	const double depth = ui.planeDepth->value(),
+				 condAngle = ui.condAngle->value();
+
+	QTemporaryFile indat, outdat;
+	QString inDATName, outDATName;
+
+	// Make temp files for the input and output files
+	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
+		return;
+
+	// Compose the command-line arguments
+	QString argv = join (
+	{
+		(axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
+		(mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
+		depth,
+		"-a",
+		condAngle,
+		inDATName,
+		outDATName
+	});
+
+	writeSelection (inDATName);
+
+	if (!runUtilityProcess (Ytruder, prog_ytruder, argv))
+		return;
+
+	insertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+// Rectifier interface
+// =============================================================================
+DEFINE_ACTION (Rectifier, 0)
+{
+	setlocale (LC_ALL, "C");
+
+	if (!checkProgPath (Rectifier))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::RectifierUI ui;
+	ui.setupUi (dlg);
+
+	if (!dlg->exec())
+		return;
+
+	QTemporaryFile indat, outdat;
+	QString inDATName, outDATName;
+
+	// Make temp files for the input and output files
+	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
+		return;
+
+	// Compose arguments
+	QString argv = join (
+	{
+		(!ui.cb_condense->isChecked()) ? "-q" : "",
+		(!ui.cb_subst->isChecked()) ? "-r" : "",
+		(ui.cb_condlineCheck->isChecked()) ? "-a" : "",
+		(ui.cb_colorize->isChecked()) ? "-c" : "",
+		"-t",
+		ui.dsb_coplthres->value(),
+		inDATName,
+		outDATName
+	});
+
+	writeSelection (inDATName);
+
+	if (!runUtilityProcess (Rectifier, prog_rectifier, argv))
+		return;
+
+	insertOutput (outDATName, true, {});
+}
+
+// =============================================================================
+// Intersector interface
+// =============================================================================
+DEFINE_ACTION (Intersector, 0)
+{
+	setlocale (LC_ALL, "C");
+
+	if (!checkProgPath (Intersector))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::IntersectorUI ui;
+	ui.setupUi (dlg);
+
+	makeColorComboBox (ui.cmb_incol);
+	makeColorComboBox (ui.cmb_cutcol);
+	ui.cb_repeat->setWhatsThis ("If this is set, " APPNAME " runs Intersector a second time with inverse files to cut the "
+								" cutter group with the input group. Both groups are cut by the intersection.");
+	ui.cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection.");
+
+	int inCol, cutCol;
+	const bool repeatInverse = ui.cb_repeat->isChecked();
+
+	forever
+	{
+		if (!dlg->exec())
+			return;
+
+		inCol = ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt();
+		cutCol =  ui.cmb_cutcol->itemData (ui.cmb_cutcol->currentIndex()).toInt();
+
+		if (inCol == cutCol)
+		{
+			critical ("Cannot use the same color group for both input and cutter!");
+			continue;
+		}
+
+		break;
+	}
+
+	// Five temporary files!
+	// indat = input group file
+	// cutdat = cutter group file
+	// outdat = primary output
+	// outdat2 = inverse output
+	// edgesdat = edges output (isecalc)
+	QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat;
+	QString inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
+
+	if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) ||
+			!mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) ||
+			!mkTempFile (edgesdat, edgesDATName))
+	{
+		return;
+	}
+
+	QString parms = join (
+	{
+		(ui.cb_colorize->isChecked()) ? "-c" : "",
+		(ui.cb_nocondense->isChecked()) ? "-t" : "",
+		"-s",
+		ui.dsb_prescale->value()
+	});
+
+	QString argv_normal = join (
+	{
+		parms,
+		inDATName,
+		cutDATName,
+		outDATName
+	});
+
+	QString argv_inverse = join (
+	{
+		parms,
+		cutDATName,
+		inDATName,
+		outDAT2Name
+	});
+
+	writeColorGroup (inCol, inDATName);
+	writeColorGroup (cutCol, cutDATName);
+
+	if (!runUtilityProcess (Intersector, prog_intersector, argv_normal))
+		return;
+
+	insertOutput (outDATName, false, {inCol});
+
+	if (repeatInverse && runUtilityProcess (Intersector, prog_intersector, argv_inverse))
+		insertOutput (outDAT2Name, false, {cutCol});
+
+	if (
+		ui.cb_edges->isChecked() &&
+		checkProgPath (Isecalc) &&
+		runUtilityProcess (Isecalc, prog_isecalc, join ( {inDATName, cutDATName, edgesDATName}))
+	)
+		insertOutput (edgesDATName, false, {});
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Coverer, 0)
+{
+	setlocale (LC_ALL, "C");
+
+	if (!checkProgPath (Coverer))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::CovererUI ui;
+	ui.setupUi (dlg);
+	makeColorComboBox (ui.cmb_col1);
+	makeColorComboBox (ui.cmb_col2);
+
+	int in1Col, in2Col;
+
+	forever
+	{
+		if (!dlg->exec())
+			return;
+
+		in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt();
+		in2Col = ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt();
+
+		if (in1Col == in2Col)
+		{
+			critical ("Cannot use the same color group for both input and cutter!");
+			continue;
+		}
+
+		break;
+	}
+
+	QTemporaryFile in1dat, in2dat, outdat;
+	QString in1DATName, in2DATName, outDATName;
+
+	if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
+		return;
+
+	QString argv = join (
+	{
+		(ui.cb_oldsweep->isChecked() ? "-s" : ""),
+		(ui.cb_reverse->isChecked() ? "-r" : ""),
+		(ui.dsb_segsplit->value() != 0 ? format ("-l %1", ui.dsb_segsplit->value()) : ""),
+		(ui.sb_bias->value() != 0 ? format ("-s %1", ui.sb_bias->value()) : ""),
+		in1DATName,
+		in2DATName,
+		outDATName
+	});
+
+	writeColorGroup (in1Col, in1DATName);
+	writeColorGroup (in2Col, in2DATName);
+
+	if (!runUtilityProcess (Coverer, prog_coverer, argv))
+		return;
+
+	insertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Isecalc, 0)
+{
+	setlocale (LC_ALL, "C");
+
+	if (!checkProgPath (Isecalc))
+		return;
+
+	Ui::IsecalcUI ui;
+	QDialog* dlg = new QDialog;
+	ui.setupUi (dlg);
+
+	makeColorComboBox (ui.cmb_col1);
+	makeColorComboBox (ui.cmb_col2);
+
+	int in1Col, in2Col;
+
+	// Run the dialog and validate input
+	forever
+	{
+		if (!dlg->exec())
+			return;
+
+		in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(),
+		in2Col = ui.cmb_col1->itemData (ui.cmb_col2->currentIndex()).toInt();
+
+		if (in1Col == in2Col)
+		{
+			critical ("Cannot use the same color group for both input and cutter!");
+			continue;
+		}
+
+		break;
+	}
+
+	QTemporaryFile in1dat, in2dat, outdat;
+	QString in1DATName, in2DATName, outDATName;
+
+	if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
+		return;
+
+	QString argv = join (
+	{
+		in1DATName,
+		in2DATName,
+		outDATName
+	});
+
+	writeColorGroup (in1Col, in1DATName);
+	writeColorGroup (in2Col, in2DATName);
+	runUtilityProcess (Isecalc, prog_isecalc, argv);
+	insertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Edger2, 0)
+{
+	setlocale (LC_ALL, "C");
+
+	if (!checkProgPath (Edger2))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::Edger2Dialog ui;
+	ui.setupUi (dlg);
+
+	if (!dlg->exec())
+		return;
+
+	QTemporaryFile in, out;
+	QString inName, outName;
+
+	if (!mkTempFile (in, inName) || !mkTempFile (out, outName))
+		return;
+
+	int unmatched = ui.unmatched->currentIndex();
+
+	QString argv = join (
+	{
+		format ("-p %1", ui.precision->value()),
+		format ("-af %1", ui.flatAngle->value()),
+		format ("-ac %1", ui.condAngle->value()),
+		format ("-ae %1", ui.edgeAngle->value()),
+		ui.delLines->isChecked()     ? "-de" : "",
+		ui.delCondLines->isChecked() ? "-dc" : "",
+		ui.colored->isChecked()      ? "-c" : "",
+		ui.bfc->isChecked()          ? "-b" : "",
+		ui.convex->isChecked()       ? "-cx" : "",
+		ui.concave->isChecked()      ? "-cv" : "",
+		unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""),
+		inName,
+		outName,
+	});
+
+	writeSelection (inName);
+
+	if (!runUtilityProcess (Edger2, prog_edger2, argv))
+		return;
+
+	insertOutput (outName, true, {});
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/format.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,172 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QString>
+#include "basics.h"
+
+//! \file Format.h
+//! Contains string formatting-related functions and classes.
+
+//!
+//! Converts a given value into a string that can be retrieved with text().
+//! Used as the argument type to the formatting functions, hence its name.
+//!
+class StringFormatArg
+{
+	public:
+		StringFormatArg (const QString& a) : m_text (a) {}
+		StringFormatArg (const char& a) : m_text (a) {}
+		StringFormatArg (const uchar& a) : m_text (a) {}
+		StringFormatArg (const QChar& a) : m_text (a) {}
+		StringFormatArg (int a) : m_text (QString::number (a)) {}
+		StringFormatArg (long a) : m_text (QString::number (a)) {}
+		StringFormatArg (const float& a) : m_text (QString::number (a)) {}
+		StringFormatArg (const double& a) : m_text (QString::number (a)) {}
+		StringFormatArg (const Vertex& a) : m_text (a.toString (false)) {}
+		StringFormatArg (const Matrix& a) : m_text (a.toString()) {}
+		StringFormatArg (const char* a) : m_text (a) {}
+
+		StringFormatArg (const void* a)
+		{
+			m_text.sprintf ("%p", a);
+		}
+
+		template<typename T>
+		StringFormatArg (const QList<T>& a)
+		{
+			m_text = "{";
+
+			for (const T& it : a)
+			{
+				if (&it != &a.first())
+					m_text += ", ";
+
+				StringFormatArg arg (it);
+				m_text += arg.text();
+			}
+
+			m_text += "}";
+		}
+
+		inline QString text() const
+		{
+			return m_text;
+		}
+
+	private:
+		QString m_text;
+};
+
+//!
+//! Helper function for \c format
+//!
+template<typename Arg1, typename... Rest>
+void formatHelper (QString& str, Arg1 arg1, Rest... rest)
+{
+	str = str.arg (StringFormatArg (arg1).text());
+	formatHelper (str, rest...);
+}
+
+//!
+//! Overload of \c formatHelper() with no template args
+//!
+static void formatHelper (QString& str) __attribute__ ((unused));
+static void formatHelper (QString& str)
+{
+	(void) str;
+}
+
+//!
+//! @brief Format the message with the given args.
+//!
+//! The formatting ultimately uses QString's arg() method to actually format
+//! the args so the format string should be prepared accordingly, with %1
+//! referring to the first arg, %2 to the second, etc.
+//!
+//! \param fmtstr The string to format
+//! \param args The args to format with
+//! \return The formatted string
+//!
+template<typename... Args>
+QString format (QString fmtstr, Args... args)
+{
+	formatHelper (fmtstr, args...);
+	return fmtstr;
+}
+
+//!
+//! From MessageLog.cc - declared here so that I don't need to include
+//! messageLog.h here. Prints the given message to log.
+//!
+void printToLog (const QString& msg);
+
+//!
+//! Format and print the given args to the message log.
+//! \param fmtstr The string to format
+//! \param args The args to format with
+//!
+template<typename... Args>
+void print (QString fmtstr, Args... args)
+{
+	formatHelper (fmtstr, args...);
+	printToLog (fmtstr);
+}
+
+//!
+//! Format and print the given args to the given file descriptor
+//! \param fp The file descriptor to print to
+//! \param fmtstr The string to format
+//! \param args The args to format with
+//!
+template<typename... Args>
+void fprint (FILE* fp, QString fmtstr, Args... args)
+{
+	formatHelper (fmtstr, args...);
+	fprintf (fp, "%s", qPrintable (fmtstr));
+}
+
+//!
+//! Overload of \c fprint with a QIODevice
+//! \param dev The IO device to print to
+//! \param fmtstr The string to format
+//! \param args The args to format with
+//!
+template<typename... Args>
+void fprint (QIODevice& dev, QString fmtstr, Args... args)
+{
+	formatHelper (fmtstr, args...);
+	dev.write (fmtstr.toUtf8());
+}
+
+//!
+//! Exactly like print() except no-op in release builds.
+//! \param fmtstr The string to format
+//! \param args The args to format with
+//!
+template<typename... Args>
+void dprint (QString fmtstr, Args... args)
+{
+#ifndef RELEASE
+	formatHelper (fmtstr, args...);
+	printToLog (fmtstr);
+#else
+	(void) fmtstr;
+	(void) args;
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glRenderer.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,2225 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QGLWidget>
+#include <QWheelEvent>
+#include <QMouseEvent>
+#include <QContextMenuEvent>
+#include <QInputDialog>
+#include <QToolTip>
+#include <QTimer>
+#include <GL/glu.h>
+
+#include "main.h"
+#include "configuration.h"
+#include "ldDocument.h"
+#include "glRenderer.h"
+#include "colors.h"
+#include "mainWindow.h"
+#include "miscallenous.h"
+#include "editHistory.h"
+#include "dialogs.h"
+#include "addObjectDialog.h"
+#include "messageLog.h"
+#include "primitives.h"
+#include "misc/ringFinder.h"
+
+static const LDFixedCameraInfo g_FixedCameras[6] =
+{
+	{{  1,  0, 0 }, X, Z, false, false },
+	{{  0,  0, 0 }, X, Y, false,  true },
+	{{  0,  1, 0 }, Z, Y,  true,  true },
+	{{ -1,  0, 0 }, X, Z, false,  true },
+	{{  0,  0, 0 }, X, Y,  true,  true },
+	{{  0, -1, 0 }, Z, Y, false,  true },
+};
+
+// Matrix templates for circle drawing. 2 is substituted with
+// the scale value, 1 is inverted to -1 if needed.
+static const Matrix g_circleDrawMatrixTemplates[3] =
+{
+	{ 2, 0, 0, 0, 1, 0, 0, 0, 2 },
+	{ 2, 0, 0, 0, 0, 2, 0, 1, 0 },
+	{ 0, 1, 0, 2, 0, 0, 0, 0, 2 },
+};
+
+cfg (String,	gl_bgcolor,				"#FFFFFF")
+cfg (String,	gl_maincolor,			"#A0A0A0")
+cfg (Float,		gl_maincolor_alpha,		1.0)
+cfg (String,	gl_selectcolor,			"#0080FF")
+cfg (Int,		gl_linethickness,		2)
+cfg (Bool,		gl_colorbfc,			false)
+cfg (Int,		gl_camera,				GLRenderer::EFreeCamera)
+cfg (Bool,		gl_blackedges,			false)
+cfg (Bool,		gl_axes,				false)
+cfg (Bool,		gl_wireframe,			false)
+cfg (Bool,		gl_logostuds,			false)
+cfg (Bool,		gl_aa,					true)
+cfg (Bool,		gl_linelengths,			true)
+cfg (Bool,		gl_drawangles,			false)
+
+// argh
+const char* g_CameraNames[7] =
+{
+	QT_TRANSLATE_NOOP ("GLRenderer",  "Top"),
+	QT_TRANSLATE_NOOP ("GLRenderer",  "Front"),
+	QT_TRANSLATE_NOOP ("GLRenderer",  "Left"),
+	QT_TRANSLATE_NOOP ("GLRenderer",  "Bottom"),
+	QT_TRANSLATE_NOOP ("GLRenderer",  "Back"),
+	QT_TRANSLATE_NOOP ("GLRenderer",  "Right"),
+	QT_TRANSLATE_NOOP ("GLRenderer",  "Free")
+};
+
+const GL::EFixedCamera g_Cameras[7] =
+{
+	GL::ETopCamera,
+	GL::EFrontCamera,
+	GL::ELeftCamera,
+	GL::EBottomCamera,
+	GL::EBackCamera,
+	GL::ERightCamera,
+	GL::EFreeCamera
+};
+
+// Definitions for visual axes, drawn on the screen
+const struct LDGLAxis
+{
+	const QColor col;
+	const Vertex vert;
+} g_GLAxes[3] =
+{
+	{ QColor (255,   0,   0), Vertex (10000, 0, 0) }, // X
+	{ QColor (80,  192,   0), Vertex (0, 10000, 0) }, // Y
+	{ QColor (0,   160, 192), Vertex (0, 0, 10000) }, // Z
+};
+
+static bool g_glInvert = false;
+static QList<int> g_warnedColors;
+
+// =============================================================================
+//
+GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent)
+{
+	m_isPicking = m_rangepick = false;
+	m_camera = (GL::EFixedCamera) gl_camera;
+	m_drawToolTip = false;
+	m_editMode = ESelectMode;
+	m_rectdraw = false;
+	m_panning = false;
+	setDocument (null);
+	setDrawOnly (false);
+	setMessageLog (null);
+	m_width = m_height = -1;
+	m_hoverpos = g_origin;
+
+	m_toolTipTimer = new QTimer (this);
+	m_toolTipTimer->setSingleShot (true);
+	connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer()));
+
+	m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
+	m_thinBorderPen = m_thickBorderPen;
+	m_thinBorderPen.setWidth (1);
+
+	// Init camera icons
+	for (const GL::EFixedCamera cam : g_Cameras)
+	{
+		QString iconname = format ("camera-%1", tr (g_CameraNames[cam]).toLower());
+
+		CameraIcon* info = &m_cameraIcons[cam];
+		info->img = new QPixmap (getIcon (iconname));
+		info->cam = cam;
+	}
+
+	calcCameraIcons();
+}
+
+// =============================================================================
+//
+GLRenderer::~GLRenderer()
+{
+	for (int i = 0; i < 6; ++i)
+		delete currentDocumentData().overlays[i].img;
+
+	for (CameraIcon& info : m_cameraIcons)
+		delete info.img;
+}
+
+// =============================================================================
+// Calculates the "hitboxes" of the camera icons so that we can tell when the
+// cursor is pointing at the camera icon.
+//
+void GLRenderer::calcCameraIcons()
+{
+	int i = 0;
+
+	for (CameraIcon& info : m_cameraIcons)
+	{
+		// MATH
+		const long x1 = (m_width - (info.cam != EFreeCamera ? 48 : 16)) + ((i % 3) * 16) - 1,
+			y1 = ((i / 3) * 16) + 1;
+
+		info.srcRect = QRect (0, 0, 16, 16);
+		info.destRect = QRect (x1, y1, 16, 16);
+		info.selRect = QRect (
+			info.destRect.x(),
+			info.destRect.y(),
+			info.destRect.width() + 1,
+			info.destRect.height() + 1
+		);
+
+		++i;
+	}
+}
+
+// =============================================================================
+//
+void GLRenderer::initGLData()
+{
+	glEnable (GL_BLEND);
+	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	glEnable (GL_POLYGON_OFFSET_FILL);
+	glPolygonOffset (1.0f, 1.0f);
+
+	glEnable (GL_DEPTH_TEST);
+	glShadeModel (GL_SMOOTH);
+	glEnable (GL_MULTISAMPLE);
+
+	if (gl_aa)
+	{
+		glEnable (GL_LINE_SMOOTH);
+		glEnable (GL_POLYGON_SMOOTH);
+		glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
+		glHint (GL_POLYGON_SMOOTH_HINT, GL_NICEST);
+	} else
+	{
+		glDisable (GL_LINE_SMOOTH);
+		glDisable (GL_POLYGON_SMOOTH);
+	}
+}
+
+// =============================================================================
+//
+void GLRenderer::resetAngles()
+{
+	rot (X) = 30.0f;
+	rot (Y) = 325.f;
+	pan (X) = pan (Y) = rot (Z) = 0.0f;
+	zoomToFit();
+}
+
+// =============================================================================
+//
+void GLRenderer::resetAllAngles()
+{
+	EFixedCamera oldcam = camera();
+
+	for (int i = 0; i < 7; ++i)
+	{
+		setCamera ((EFixedCamera) i);
+		resetAngles();
+	}
+
+	setCamera (oldcam);
+}
+
+// =============================================================================
+//
+void GLRenderer::initializeGL()
+{
+	setBackground();
+
+	glLineWidth (gl_linethickness);
+
+	setAutoFillBackground (false);
+	setMouseTracking (true);
+	setFocusPolicy (Qt::WheelFocus);
+	compileAllObjects();
+}
+
+// =============================================================================
+//
+QColor GLRenderer::getMainColor()
+{
+	QColor col (gl_maincolor);
+
+	if (!col.isValid())
+		return QColor (0, 0, 0);
+
+	col.setAlpha (gl_maincolor_alpha * 255.f);
+	return col;
+}
+
+// =============================================================================
+//
+void GLRenderer::setBackground()
+{
+	QColor col (gl_bgcolor);
+
+	if (!col.isValid())
+		return;
+
+	col.setAlpha (255);
+
+	m_darkbg = luma (col) < 80;
+	m_bgcolor = col;
+	qglClearColor (col);
+}
+
+// =============================================================================
+//
+void GLRenderer::setObjectColor (LDObject* obj, const ListType list)
+{
+	QColor qcol;
+
+	if (!obj->isColored())
+		return;
+
+	if (list == GL::PickList)
+	{
+		// Make the color by the object's ID if we're picking, so we can make the
+		// ID again from the color we get from the picking results. Be sure to use
+		// the top level parent's index since we want a subfile's children point
+		// to the subfile itself.
+		long i = obj->topLevelParent()->id();
+
+		// Calculate a color based from this index. This method caters for
+		// 16777216 objects. I don't think that'll be exceeded anytime soon. :)
+		// ATM biggest is 53588.dat with 12600 lines.
+		double r = (i / 0x10000) % 0x100,
+			   g = (i / 0x100) % 0x100,
+			   b = i % 0x100;
+
+		qglColor (QColor (r, g, b));
+		return;
+	}
+
+	if ((list == BFCFrontList || list == BFCBackList) &&
+		obj->type() != LDObject::ELine &&
+		obj->type() != LDObject::ECondLine)
+	{
+		if (list == GL::BFCFrontList)
+			qcol = QColor (40, 192, 0);
+		else
+			qcol = QColor (224, 0, 0);
+	}
+	else
+	{
+		if (obj->color() == maincolor)
+			qcol = getMainColor();
+		else
+		{
+			LDColor* col = ::getColor (obj->color());
+
+			if (col)
+				qcol = col->faceColor;
+		}
+
+		if (obj->color() == edgecolor)
+		{
+			LDColor* col;
+
+			if (!gl_blackedges && obj->parent() && (col = ::getColor (obj->parent()->color())))
+				qcol = col->edgeColor;
+			else
+				qcol = (m_darkbg == false) ? Qt::black : Qt::white;
+		}
+
+		if (qcol.isValid() == false)
+		{
+			// The color was unknown. Use main color to make the object at least
+			// not appear pitch-black.
+			if (obj->color() != edgecolor)
+				qcol = getMainColor();
+
+			// Warn about the unknown colors, but only once.
+			for (int i : g_warnedColors)
+				if (obj->color() == i)
+					return;
+
+			print ("%1: Unknown color %2!\n", __func__, obj->color());
+			g_warnedColors << obj->color();
+			return;
+		}
+	}
+
+	int r = qcol.red(),
+		 g = qcol.green(),
+		 b = qcol.blue(),
+		 a = qcol.alpha();
+
+	if (obj->topLevelParent()->isSelected())
+	{
+		// Brighten it up for the select list.
+		QColor selcolor (gl_selectcolor);
+		r = (r + selcolor.red()) / 2;
+		g = (g + selcolor.green()) / 2;
+		b = (b + selcolor.blue()) / 2;
+	}
+
+	glColor4f (
+		((double) r) / 255.0f,
+		((double) g) / 255.0f,
+		((double) b) / 255.0f,
+		((double) a) / 255.0f);
+}
+
+// =============================================================================
+//
+void GLRenderer::refresh()
+{
+	update();
+	swapBuffers();
+}
+
+// =============================================================================
+//
+void GLRenderer::hardRefresh()
+{
+	compileAllObjects();
+	refresh();
+
+	glLineWidth (gl_linethickness);
+}
+
+// =============================================================================
+//
+void GLRenderer::resizeGL (int w, int h)
+{
+	m_width = w;
+	m_height = h;
+
+	calcCameraIcons();
+
+	glViewport (0, 0, w, h);
+	glMatrixMode (GL_PROJECTION);
+	glLoadIdentity();
+	gluPerspective (45.0f, (double) w / (double) h, 1.0f, 10000.0f);
+	glMatrixMode (GL_MODELVIEW);
+}
+
+// =============================================================================
+//
+void GLRenderer::drawGLScene()
+{
+	if (document() == null)
+		return;
+
+	if (gl_wireframe && !isPicking())
+		glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
+
+	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	glEnable (GL_DEPTH_TEST);
+
+	if (m_camera != EFreeCamera)
+	{
+		glMatrixMode (GL_PROJECTION);
+		glPushMatrix();
+
+		glLoadIdentity();
+		glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f);
+		glTranslatef (pan (X), pan (Y), 0.0f);
+
+		if (m_camera != EFrontCamera && m_camera != EBackCamera)
+		{
+			glRotatef (90.0f, g_FixedCameras[camera()].glrotate[0],
+				g_FixedCameras[camera()].glrotate[1],
+				g_FixedCameras[camera()].glrotate[2]);
+		}
+
+		// Back camera needs to be handled differently
+		if (m_camera == GLRenderer::EBackCamera)
+		{
+			glRotatef (180.0f, 1.0f, 0.0f, 0.0f);
+			glRotatef (180.0f, 0.0f, 0.0f, 1.0f);
+		}
+	}
+	else
+	{
+		glMatrixMode (GL_MODELVIEW);
+		glPushMatrix();
+		glLoadIdentity();
+
+		glTranslatef (0.0f, 0.0f, -2.0f);
+		glTranslatef (pan (X), pan (Y), -zoom());
+		glRotatef (rot (X), 1.0f, 0.0f, 0.0f);
+		glRotatef (rot (Y), 0.0f, 1.0f, 0.0f);
+		glRotatef (rot (Z), 0.0f, 0.0f, 1.0f);
+	}
+
+	const GL::ListType list = (!isDrawOnly() && isPicking()) ? PickList : NormalList;
+
+	if (gl_colorbfc && !isPicking() && !isDrawOnly())
+	{
+		glEnable (GL_CULL_FACE);
+
+		for (LDObject* obj : document()->objects())
+		{
+			if (obj->isHidden())
+				continue;
+
+			glCullFace (GL_BACK);
+			glCallList (obj->glLists[BFCFrontList]);
+
+			glCullFace (GL_FRONT);
+			glCallList (obj->glLists[BFCBackList]);
+		}
+
+		glDisable (GL_CULL_FACE);
+	}
+	else
+	{
+		for (LDObject* obj : document()->objects())
+		{
+			if (obj->isHidden())
+				continue;
+
+			glCallList (obj->glLists[list]);
+		}
+	}
+
+	if (gl_axes && !isPicking() && !isDrawOnly())
+		glCallList (m_axeslist);
+
+	glPopMatrix();
+	glMatrixMode (GL_MODELVIEW);
+	glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
+}
+
+// =============================================================================
+//
+// This converts a 2D point on the screen to a 3D point in the model. If 'snap'
+// is true, the 3D point will snap to the current grid.
+//
+Vertex GLRenderer::coordconv2_3 (const QPoint& pos2d, bool snap) const
+{
+	assert (camera() != EFreeCamera);
+
+	Vertex pos3d;
+	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
+	const Axis axisX = cam->axisX;
+	const Axis axisY = cam->axisY;
+	const int negXFac = cam->negX ? -1 : 1,
+				negYFac = cam->negY ? -1 : 1;
+
+	// Calculate cx and cy - these are the LDraw unit coords the cursor is at.
+	double cx = (-m_virtWidth + ((2 * pos2d.x() * m_virtWidth) / m_width) - pan (X));
+	double cy = (m_virtHeight - ((2 * pos2d.y() * m_virtHeight) / m_height) - pan (Y));
+
+	if (snap)
+	{
+		cx = Grid::snap (cx, (Grid::Config) axisX);
+		cy = Grid::snap (cy, (Grid::Config) axisY);
+	}
+
+	cx *= negXFac;
+	cy *= negYFac;
+
+	roundToDecimals (cx, 4);
+	roundToDecimals (cy, 4);
+
+	// Create the vertex from the coordinates
+	pos3d[axisX] = cx;
+	pos3d[axisY] = cy;
+	pos3d[3 - axisX - axisY] = getDepthValue();
+	return pos3d;
+}
+
+// =============================================================================
+//
+// Inverse operation for the above - convert a 3D position to a 2D screen
+// position. Don't ask me how this code manages to work, I don't even know.
+//
+QPoint GLRenderer::coordconv3_2 (const Vertex& pos3d) const
+{
+	GLfloat m[16];
+	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
+	const Axis axisX = cam->axisX;
+	const Axis axisY = cam->axisY;
+	const int negXFac = cam->negX ? -1 : 1,
+				negYFac = cam->negY ? -1 : 1;
+
+	glGetFloatv (GL_MODELVIEW_MATRIX, m);
+
+	const double x = pos3d.x();
+	const double y = pos3d.y();
+	const double z = pos3d.z();
+
+	Vertex transformed;
+	transformed[X] = (m[0] * x) + (m[1] * y) + (m[2] * z) + m[3];
+	transformed[Y] = (m[4] * x) + (m[5] * y) + (m[6] * z) + m[7];
+	transformed[Z] = (m[8] * x) + (m[9] * y) + (m[10] * z) + m[11];
+
+	double rx = (((transformed[axisX] * negXFac) + m_virtWidth + pan (X)) * m_width) / (2 * m_virtWidth);
+	double ry = (((transformed[axisY] * negYFac) - m_virtHeight + pan (Y)) * m_height) / (2 * m_virtHeight);
+
+	return QPoint (rx, -ry);
+}
+
+// =============================================================================
+//
+void GLRenderer::paintEvent (QPaintEvent* ev)
+{
+	Q_UNUSED (ev)
+
+	makeCurrent();
+	m_virtWidth = zoom();
+	m_virtHeight = (m_height * m_virtWidth) / m_width;
+
+	initGLData();
+	drawGLScene();
+
+	const QPen textpen (m_darkbg ? Qt::white : Qt::black);
+	const QBrush polybrush (QColor (64, 192, 0, 128));
+	QPainter paint (this);
+	QFontMetrics metrics = QFontMetrics (QFont());
+	paint.setRenderHint (QPainter::HighQualityAntialiasing);
+
+	// If we wish to only draw the brick, stop here
+	if (isDrawOnly())
+		return;
+
+	if (m_camera != EFreeCamera && !isPicking())
+	{
+		// Paint the overlay image if we have one
+		const LDGLOverlay& overlay = currentDocumentData().overlays[m_camera];
+
+		if (overlay.img != null)
+		{
+			QPoint v0 = coordconv3_2 (currentDocumentData().overlays[m_camera].v0),
+					   v1 = coordconv3_2 (currentDocumentData().overlays[m_camera].v1);
+
+			QRect targRect (v0.x(), v0.y(), abs (v1.x() - v0.x()), abs (v1.y() - v0.y())),
+				  srcRect (0, 0, overlay.img->width(), overlay.img->height());
+			paint.drawImage (targRect, *overlay.img, srcRect);
+		}
+
+		// Paint the coordinates onto the screen.
+		QString text = format (tr ("X: %1, Y: %2, Z: %3"), m_hoverpos[X], m_hoverpos[Y], m_hoverpos[Z]);
+		QFontMetrics metrics = QFontMetrics (font());
+		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
+		paint.setPen (textpen);
+		paint.drawText (m_width - textSize.width(), m_height - 16, textSize.width(),
+			textSize.height(), Qt::AlignCenter, text);
+
+		QPen linepen = m_thinBorderPen;
+		linepen.setWidth (2);
+		linepen.setColor (luma (m_bgcolor) < 40 ? Qt::white : Qt::black);
+
+		// Mode-specific rendering
+		if (editMode() == EDrawMode)
+		{
+			QPoint poly[4];
+			Vertex poly3d[4];
+			int numverts = 4;
+
+			// Calculate polygon data
+			if (!m_rectdraw)
+			{
+				numverts = m_drawedVerts.size() + 1;
+				int i = 0;
+
+				for (Vertex& vert : m_drawedVerts)
+					poly3d[i++] = vert;
+
+				// Draw the cursor vertex as the last one in the list.
+				if (numverts <= 4)
+					poly3d[i] = m_hoverpos;
+				else
+					numverts = 4;
+			}
+			else
+			{
+				// Get vertex information from m_rectverts
+				if (m_drawedVerts.size() > 0)
+					for (int i = 0; i < numverts; ++i)
+						poly3d[i] = m_rectverts[i];
+				else
+					poly3d[0] = m_hoverpos;
+			}
+
+			// Convert to 2D
+			for (int i = 0; i < numverts; ++i)
+				poly[i] = coordconv3_2 (poly3d[i]);
+
+			if (numverts > 0)
+			{
+				// Draw the polygon-to-be
+				paint.setBrush (polybrush);
+				paint.drawPolygon (poly, numverts);
+
+				// Draw vertex blips
+				for (int i = 0; i < numverts; ++i)
+				{
+					QPoint& blip = poly[i];
+					paint.setPen (linepen);
+					drawBlip (paint, blip);
+
+					// Draw their coordinates
+					paint.setPen (textpen);
+					paint.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true));
+				}
+
+				// Draw line lenghts and angle info if appropriate
+				if (numverts >= 2)
+				{
+					int numlines = (m_drawedVerts.size() == 1) ? 1 : m_drawedVerts.size() + 1;
+					paint.setPen (textpen);
+
+					for (int i = 0; i < numlines; ++i)
+					{
+						const int j = (i + 1 < numverts) ? i + 1 : 0;
+						const int h = (i - 1 >= 0) ? i - 1 : numverts - 1;
+
+						if (gl_linelengths)
+						{
+							const QString label = QString::number (poly3d[i].distanceTo (poly3d[j]));
+							QPoint origin = QLineF (poly[i], poly[j]).pointAt (0.5).toPoint();
+							paint.drawText (origin, label);
+						}
+
+						if (gl_drawangles)
+						{
+							QLineF l0 (poly[h], poly[i]),
+								l1 (poly[i], poly[j]);
+
+							double angle = 180 - l0.angleTo (l1);
+
+							if (angle < 0)
+								angle = 180 - l1.angleTo (l0);
+
+							QString label = QString::number (angle) + QString::fromUtf8 (QByteArray ("\302\260"));
+							QPoint pos = poly[i];
+							pos.setY (pos.y() + metrics.height());
+
+							paint.drawText (pos, label);
+						}
+					}
+				}
+			}
+		}
+		elif (editMode() == ECircleMode)
+		{
+			// If we have not specified the center point of the circle yet, preview it on the screen.
+			if (m_drawedVerts.isEmpty())
+				drawBlip (paint, coordconv3_2 (m_hoverpos));
+			else
+			{
+				QVector<Vertex> verts, verts2;
+				const double dist0 = getCircleDrawDist (0),
+					dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1;
+				const int segs = g_lores;
+				const double angleUnit = (2 * pi) / segs;
+				Axis relX, relY;
+				QVector<QPoint> ringpoints, circlepoints, circle2points;
+
+				getRelativeAxes (relX, relY);
+
+				// Calculate the preview positions of vertices
+				for (int i = 0; i < segs; ++i)
+				{
+					Vertex v = g_origin;
+					v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0);
+					v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0);
+					verts << v;
+
+					if (dist1 != -1)
+					{
+						v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1);
+						v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist1);
+						verts2 << v;
+					}
+				}
+
+				int i = 0;
+				for (const Vertex& v : verts + verts2)
+				{
+					// Calculate the 2D point of the vertex
+					QPoint point = coordconv3_2 (v);
+
+					// Draw a green blip at where it is
+					drawBlip (paint, point);
+
+					// Add it to the list of points for the green ring fill.
+					ringpoints << point;
+
+					// Also add the circle points to separate lists
+					if (i < verts.size())
+						circlepoints << point;
+					else
+						circle2points << point;
+
+					++i;
+				}
+
+				// Insert the first point as the seventeenth one so that
+				// the ring polygon is closed properly.
+				if (ringpoints.size() >= 16)
+					ringpoints.insert (16, ringpoints[0]);
+
+				// Same for the outer ring. Note that the indices are offset by 1
+				// because of the insertion done above bumps the values.
+				if (ringpoints.size() >= 33)
+					ringpoints.insert (33, ringpoints[17]);
+
+				// Draw the ring
+				paint.setBrush ((m_drawedVerts.size() >= 2) ? polybrush : Qt::NoBrush);
+				paint.setPen (Qt::NoPen);
+				paint.drawPolygon (QPolygon (ringpoints));
+
+				// Draw the circles
+				paint.setBrush (Qt::NoBrush);
+				paint.setPen (linepen);
+				paint.drawPolygon (QPolygon (circlepoints));
+				paint.drawPolygon (QPolygon (circle2points));
+
+				{ // Draw the current radius in the middle of the circle.
+					QPoint origin = coordconv3_2 (m_drawedVerts[0]);
+					QString label = QString::number (dist0);
+					paint.setPen (textpen);
+					paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label);
+
+					if (m_drawedVerts.size() >= 2)
+					{
+						label = QString::number (dist1);
+						paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y() + metrics.height(), label);
+					}
+				}
+			}
+		}
+	}
+
+	// Camera icons
+	if (!isPicking())
+	{
+		// Draw a background for the selected camera
+		paint.setPen (m_thinBorderPen);
+		paint.setBrush (QBrush (QColor (0, 128, 160, 128)));
+		paint.drawRect (m_cameraIcons[camera()].selRect);
+
+		// Draw the actual icons
+		for (CameraIcon& info : m_cameraIcons)
+		{
+			// Don't draw the free camera icon when in draw mode
+			if (&info == &m_cameraIcons[GL::EFreeCamera] && editMode() != ESelectMode)
+				continue;
+
+			paint.drawPixmap (info.destRect, *info.img, info.srcRect);
+		}
+
+		QString formatstr = tr ("%1 Camera");
+
+		// Draw a label for the current camera in the bottom left corner
+		{
+			const int margin = 4;
+
+			QString label;
+			label = format (formatstr, tr (g_CameraNames[camera()]));
+			paint.setPen (textpen);
+			paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label);
+		}
+
+		// Tool tips
+		if (m_drawToolTip)
+		{
+			if (m_cameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false)
+				m_drawToolTip = false;
+			else
+			{
+				QString label = format (formatstr, tr (g_CameraNames[m_toolTipCamera]));
+				QToolTip::showText (m_globalpos, label);
+			}
+		}
+	}
+
+	// Message log
+	if (messageLog())
+	{
+		int y = 0;
+		const int margin = 2;
+		QColor penColor = textpen.color();
+
+		for (const MessageManager::Line& line : messageLog()->getLines())
+		{
+			penColor.setAlphaF (line.alpha);
+			paint.setPen (penColor);
+			paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text);
+			y += metrics.height();
+		}
+	}
+
+	// If we're range-picking, draw a rectangle encompassing the selection area.
+	if (m_rangepick && !isPicking() && m_totalmove >= 10)
+	{
+		int x0 = m_rangeStart.x(),
+			y0 = m_rangeStart.y(),
+			x1 = m_pos.x(),
+			y1 = m_pos.y();
+
+		QRect rect (x0, y0, x1 - x0, y1 - y0);
+		QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF");
+		fillColor.setAlphaF (0.2f);
+
+		paint.setPen (m_thickBorderPen);
+		paint.setBrush (QBrush (fillColor));
+		paint.drawRect (rect);
+	}
+}
+
+// =============================================================================
+//
+void GLRenderer::drawBlip (QPainter& paint, QPoint pos) const
+{
+	QPen pen = m_thinBorderPen;
+	const int blipsize = 8;
+	pen.setWidth (1);
+	paint.setPen (pen);
+	paint.setBrush (QColor (64, 192, 0));
+	paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize);
+}
+
+// =============================================================================
+//
+void GLRenderer::compileAllObjects()
+{
+	if (!document())
+		return;
+
+	// Compiling all is a big job, use a busy cursor
+	setCursor (Qt::BusyCursor);
+
+	m_knownVerts.clear();
+
+	for (LDObject* obj : document()->objects())
+		compileObject (obj);
+
+	// Compile axes
+	glDeleteLists (m_axeslist, 1);
+	m_axeslist = glGenLists (1);
+	glNewList (m_axeslist, GL_COMPILE);
+	glBegin (GL_LINES);
+
+	for (const LDGLAxis& ax : g_GLAxes)
+	{
+		qglColor (ax.col);
+		compileVertex (ax.vert);
+		compileVertex (-ax.vert);
+	}
+
+	glEnd();
+	glEndList();
+
+	setCursor (Qt::ArrowCursor);
+}
+
+// =============================================================================
+//
+void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype)
+{
+	glBegin (gltype);
+
+	const int numverts = (obj->type() != LDObject::ECondLine) ? obj->vertices() : 2;
+
+	if (g_glInvert == false)
+		for (int i = 0; i < numverts; ++i)
+			compileVertex (obj->vertex (i));
+	else
+		for (int i = numverts - 1; i >= 0; --i)
+			compileVertex (obj->vertex (i));
+
+	glEnd();
+}
+
+// =============================================================================
+//
+void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list)
+{
+	setObjectColor (obj, list);
+
+	switch (obj->type())
+	{
+		case LDObject::ELine:
+		{
+			compileSubObject (obj, GL_LINES);
+		} break;
+
+		case LDObject::ECondLine:
+		{
+			// Draw conditional lines with a dash pattern - however, use a full
+			// line when drawing a pick list to make selecting them easier.
+			if (list != GL::PickList)
+			{
+				glLineStipple (1, 0x6666);
+				glEnable (GL_LINE_STIPPLE);
+			}
+
+			compileSubObject (obj, GL_LINES);
+
+			glDisable (GL_LINE_STIPPLE);
+		} break;
+
+		case LDObject::ETriangle:
+		{
+			compileSubObject (obj, GL_TRIANGLES);
+		} break;
+
+		case LDObject::EQuad:
+		{
+			compileSubObject (obj, GL_QUADS);
+		} break;
+
+		case LDObject::ESubfile:
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			LDObjectList objs;
+
+			objs = ref->inlineContents (LDSubfile::DeepCacheInline | LDSubfile::RendererInline);
+			bool oldinvert = g_glInvert;
+
+			if (ref->transform().getDeterminant() < 0)
+				g_glInvert = !g_glInvert;
+
+			LDObject* prev = ref->previous();
+
+			if (prev && prev->type() == LDObject::EBFC && static_cast<LDBFC*> (prev)->statement() == LDBFC::InvertNext)
+				g_glInvert = !g_glInvert;
+
+			for (LDObject* obj : objs)
+			{
+				compileList (obj, list);
+				obj->destroy();
+			}
+
+			g_glInvert = oldinvert;
+		} break;
+
+		default:
+			break;
+	}
+}
+
+// =============================================================================
+//
+void GLRenderer::compileVertex (const Vertex& vrt)
+{
+	glVertex3d (vrt[X], -vrt[Y], -vrt[Z]);
+}
+
+// =============================================================================
+//
+void GLRenderer::clampAngle (double& angle) const
+{
+	while (angle < 0)
+		angle += 360.0;
+
+	while (angle > 360.0)
+		angle -= 360.0;
+}
+
+// =============================================================================
+//
+void GLRenderer::addDrawnVertex (Vertex pos)
+{
+	// If we picked an already-existing vertex, stop drawing
+	if (editMode() == EDrawMode)
+	{
+		for (Vertex& vert : m_drawedVerts)
+		{
+			if (vert == pos)
+			{
+				endDraw (true);
+				return;
+			}
+		}
+	}
+
+	m_drawedVerts << pos;
+}
+
+// =============================================================================
+//
+void GLRenderer::mouseReleaseEvent (QMouseEvent* ev)
+{
+	const bool wasLeft = (m_lastButtons & Qt::LeftButton) && ! (ev->buttons() & Qt::LeftButton),
+				   wasRight = (m_lastButtons & Qt::RightButton) && ! (ev->buttons() & Qt::RightButton),
+				   wasMid = (m_lastButtons & Qt::MidButton) && ! (ev->buttons() & Qt::MidButton);
+
+	if (m_panning)
+		m_panning = false;
+
+	if (wasLeft)
+	{
+		// Check if we selected a camera icon
+		if (!m_rangepick)
+		{
+			for (CameraIcon & info : m_cameraIcons)
+			{
+				if (info.destRect.contains (ev->pos()))
+				{
+					setCamera (info.cam);
+					goto end;
+				}
+			}
+		}
+
+		switch (editMode())
+		{
+			case EDrawMode:
+			{
+				if (m_rectdraw)
+				{
+					if (m_drawedVerts.size() == 2)
+					{
+						endDraw (true);
+						return;
+					}
+				} else
+				{
+					// If we have 4 verts, stop drawing.
+					if (m_drawedVerts.size() >= 4)
+					{
+						endDraw (true);
+						return;
+					}
+
+					if (m_drawedVerts.isEmpty() && ev->modifiers() & Qt::ShiftModifier)
+					{
+						m_rectdraw = true;
+						updateRectVerts();
+					}
+				}
+
+				addDrawnVertex (m_hoverpos);
+			} break;
+
+			case ECircleMode:
+			{
+				if (m_drawedVerts.size() == 3)
+				{
+					endDraw (true);
+					return;
+				}
+
+				addDrawnVertex (m_hoverpos);
+			} break;
+
+			case ESelectMode:
+			{
+				if (!isDrawOnly())
+				{
+					if (m_totalmove < 10)
+						m_rangepick = false;
+
+					if (!m_rangepick)
+						m_addpick = (m_keymods & Qt::ControlModifier);
+
+					if (m_totalmove < 10 || m_rangepick)
+						pick (ev->x(), ev->y());
+				}
+			} break;
+		}
+
+		m_rangepick = false;
+	}
+
+	if (wasMid && editMode() != ESelectMode && m_drawedVerts.size() < 4 && m_totalmove < 10)
+	{
+		// Find the closest vertex to our cursor
+		double mindist = 1024.0f;
+		Vertex closest;
+		bool valid = false;
+
+		QPoint curspos = coordconv3_2 (m_hoverpos);
+
+		for (const Vertex& pos3d: m_knownVerts)
+		{
+			QPoint pos2d = coordconv3_2 (pos3d);
+
+			// Measure squared distance
+			const double dx = abs (pos2d.x() - curspos.x()),
+						 dy = abs (pos2d.y() - curspos.y()),
+						 distsq = (dx * dx) + (dy * dy);
+
+			if (distsq >= 1024.0f) // 32.0f ** 2
+				continue; // too far away
+
+			if (distsq < mindist)
+			{
+				mindist = distsq;
+				closest = pos3d;
+				valid = true;
+
+				// If it's only 4 pixels away, I think we found our vertex now.
+				if (distsq <= 16.0f) // 4.0f ** 2
+					break;
+			}
+		}
+
+		if (valid)
+			addDrawnVertex (closest);
+	}
+
+	if (wasRight && !m_drawedVerts.isEmpty())
+	{
+		// Remove the last vertex
+		m_drawedVerts.removeLast();
+
+		if (m_drawedVerts.isEmpty())
+			m_rectdraw = false;
+	}
+
+end:
+	update();
+	m_totalmove = 0;
+}
+
+// =============================================================================
+//
+void GLRenderer::mousePressEvent (QMouseEvent* ev)
+{
+	m_totalmove = 0;
+
+	if (ev->modifiers() & Qt::ControlModifier)
+	{
+		m_rangepick = true;
+		m_rangeStart.setX (ev->x());
+		m_rangeStart.setY (ev->y());
+		m_addpick = (m_keymods & Qt::AltModifier);
+		ev->accept();
+	}
+
+	m_lastButtons = ev->buttons();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::mouseMoveEvent (QMouseEvent* ev)
+{
+	int dx = ev->x() - m_pos.x();
+	int dy = ev->y() - m_pos.y();
+	m_totalmove += abs (dx) + abs (dy);
+
+	const bool left = ev->buttons() & Qt::LeftButton,
+			   mid = ev->buttons() & Qt::MidButton,
+			   shift = ev->modifiers() & Qt::ShiftModifier;
+
+	if (mid || (left && shift))
+	{
+		pan (X) += 0.03f * dx * (zoom() / 7.5f);
+		pan (Y) -= 0.03f * dy * (zoom() / 7.5f);
+		m_panning = true;
+	} elif (left && !m_rangepick && camera() == EFreeCamera)
+	{
+		rot (X) = rot (X) + dy;
+		rot (Y) = rot (Y) + dx;
+
+		clampAngle (rot (X));
+		clampAngle (rot (Y));
+	}
+
+	// Start the tool tip timer
+	if (!m_drawToolTip)
+		m_toolTipTimer->start (500);
+
+	// Update 2d position
+	m_pos = ev->pos();
+	m_globalpos = ev->globalPos();
+
+	// Calculate 3d position of the cursor
+	m_hoverpos = (camera() != EFreeCamera) ? coordconv2_3 (m_pos, true) : g_origin;
+
+	// Update rect vertices since m_hoverpos may have changed
+	updateRectVerts();
+
+	update();
+}
+
+// =============================================================================
+//
+void GLRenderer::keyPressEvent (QKeyEvent* ev)
+{
+	m_keymods = ev->modifiers();
+}
+
+// =============================================================================
+//
+void GLRenderer::keyReleaseEvent (QKeyEvent* ev)
+{
+	m_keymods = ev->modifiers();
+}
+
+// =============================================================================
+//
+void GLRenderer::wheelEvent (QWheelEvent* ev)
+{
+	makeCurrent();
+
+	zoomNotch (ev->delta() > 0);
+	zoom() = clamp (zoom(), 0.01, 10000.0);
+
+	update();
+	ev->accept();
+}
+
+// =============================================================================
+//
+void GLRenderer::leaveEvent (QEvent* ev)
+{
+	(void) ev;
+	m_drawToolTip = false;
+	m_toolTipTimer->stop();
+	update();
+}
+
+// =============================================================================
+//
+void GLRenderer::contextMenuEvent (QContextMenuEvent* ev)
+{
+	g_win->spawnContextMenu (ev->globalPos());
+}
+
+// =============================================================================
+//
+void GLRenderer::setCamera (const GLRenderer::EFixedCamera cam)
+{
+	m_camera = cam;
+	gl_camera = (int) cam;
+	g_win->updateEditModeActions();
+}
+
+// =============================================================================
+//
+void GLRenderer::pick (int mouseX, int mouseY)
+{
+	makeCurrent();
+
+	// Use particularly thick lines while picking ease up selecting lines.
+	glLineWidth (max<double> (gl_linethickness, 6.5f));
+
+	// Clear the selection if we do not wish to add to it.
+	if (!m_addpick)
+	{
+		LDObjectList oldsel = selection();
+		getCurrentDocument()->clearSelection();
+
+		for (LDObject* obj : oldsel)
+			compileObject (obj);
+	}
+
+	setPicking (true);
+
+	// Paint the picking scene
+	glDisable (GL_DITHER);
+	glClearColor (1.0f, 1.0f, 1.0f, 1.0f);
+	drawGLScene();
+
+	int x0 = mouseX,
+		  y0 = mouseY;
+	int x1, y1;
+
+	// Determine how big an area to read - with range picking, we pick by
+	// the area given, with single pixel picking, we use an 1 x 1 area.
+	if (m_rangepick)
+	{
+		x1 = m_rangeStart.x();
+		y1 = m_rangeStart.y();
+	}
+	else
+	{
+		x1 = x0 + 1;
+		y1 = y0 + 1;
+	}
+
+	// x0 and y0 must be less than x1 and y1, respectively.
+	if (x0 > x1)
+		qSwap (x0, x1);
+
+	if (y0 > y1)
+		qSwap (y0, y1);
+
+	// Clamp the values to ensure they're within bounds
+	x0 = max (0, x0);
+	y0 = max (0, y0);
+	x1 = min (x1, m_width);
+	y1 = min (y1, m_height);
+	const int areawidth = (x1 - x0);
+	const int areaheight = (y1 - y0);
+	const qint32 numpixels = areawidth * areaheight;
+
+	// Allocate space for the pixel data.
+	uchar* const pixeldata = new uchar[4 * numpixels];
+	uchar* pixelptr = &pixeldata[0];
+
+	// Read pixels from the color buffer.
+	glReadPixels (x0, m_height - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata);
+
+	LDObject* removedObj = null;
+
+	// Go through each pixel read and add them to the selection.
+	for (qint32 i = 0; i < numpixels; ++i)
+	{
+		qint32 idx =
+			(*(pixelptr + 0) * 0x10000) +
+			(*(pixelptr + 1) * 0x00100) +
+			(*(pixelptr + 2) * 0x00001);
+		pixelptr += 4;
+
+		if (idx == 0xFFFFFF)
+			continue; // White is background; skip
+
+		LDObject* obj = LDObject::fromID (idx);
+		assert (obj != null);
+
+		// If this is an additive single pick and the object is currently selected,
+		// we remove it from selection instead.
+		if (!m_rangepick && m_addpick)
+		{
+			if (obj->isSelected())
+			{
+				obj->unselect();
+				removedObj = obj;
+				break;
+			}
+		}
+
+		obj->select();
+	}
+
+	delete[] pixeldata;
+
+	// Update everything now.
+	g_win->updateSelection();
+
+	// Recompile the objects now to update their color
+	for (LDObject* obj : selection())
+		compileObject (obj);
+
+	if (removedObj)
+		compileObject (removedObj);
+
+	// Restore line thickness
+	glLineWidth (gl_linethickness);
+
+	setPicking (false);
+	m_rangepick = false;
+	glEnable (GL_DITHER);
+
+	setBackground();
+	repaint();
+}
+
+// =============================================================================
+//
+void GLRenderer::setEditMode (EditMode const& a)
+{
+	m_editMode = a;
+
+	switch (a)
+	{
+		case ESelectMode:
+		{
+			unsetCursor();
+			setContextMenuPolicy (Qt::DefaultContextMenu);
+		} break;
+
+		case EDrawMode:
+		case ECircleMode:
+		{
+			// Cannot draw into the free camera - use top instead.
+			if (m_camera == EFreeCamera)
+				setCamera (ETopCamera);
+
+			// Disable the context menu - we need the right mouse button
+			// for removing vertices.
+			setContextMenuPolicy (Qt::NoContextMenu);
+
+			// Use the crosshair cursor when drawing.
+			setCursor (Qt::CrossCursor);
+
+			// Clear the selection when beginning to draw.
+			LDObjectList priorsel = selection();
+			getCurrentDocument()->clearSelection();
+
+			for (LDObject* obj : priorsel)
+				compileObject (obj);
+
+			g_win->updateSelection();
+			m_drawedVerts.clear();
+		} break;
+	}
+
+	g_win->updateEditModeActions();
+	update();
+}
+
+// =============================================================================
+//
+void GLRenderer::setDocument (LDDocument* const& a)
+{
+	m_document = a;
+
+	if (a != null)
+	{
+		initOverlaysFromObjects();
+
+		if (currentDocumentData().init == false)
+		{
+			resetAllAngles();
+			currentDocumentData().init = true;
+		}
+	}
+}
+
+// =============================================================================
+//
+Matrix GLRenderer::getCircleDrawMatrix (double scale)
+{
+	Matrix transform = g_circleDrawMatrixTemplates[camera() % 3];
+
+	for (int i = 0; i < 9; ++i)
+	{
+		if (transform[i] == 2)
+			transform[i] = scale;
+		elif (transform[i] == 1 && camera() >= 3)
+			transform[i] = -1;
+	}
+
+	return transform;
+}
+
+// =============================================================================
+//
+void GLRenderer::endDraw (bool accept)
+{
+	(void) accept;
+
+	// Clean the selection and create the object
+	QList<Vertex>& verts = m_drawedVerts;
+	LDObjectList objs;
+
+	switch (editMode())
+	{
+		case EDrawMode:
+		{
+			if (m_rectdraw)
+			{
+				LDQuad* quad = new LDQuad;
+
+				// Copy the vertices from m_rectverts
+				updateRectVerts();
+
+				for (int i = 0; i < quad->vertices(); ++i)
+					quad->setVertex (i, m_rectverts[i]);
+
+				quad->setColor (maincolor);
+				objs << quad;
+			}
+			else
+			{
+				switch (verts.size())
+				{
+					case 1:
+					{
+						// 1 vertex - add a vertex object
+						LDVertex* obj = new LDVertex;
+						obj->pos = verts[0];
+						obj->setColor (maincolor);
+						objs << obj;
+					} break;
+
+					case 2:
+					{
+						// 2 verts - make a line
+						LDLine* obj = new LDLine (verts[0], verts[1]);
+						obj->setColor (edgecolor);
+						objs << obj;
+					} break;
+
+					case 3:
+					case 4:
+					{
+						LDObject* obj = (verts.size() == 3) ?
+							  static_cast<LDObject*> (new LDTriangle) :
+							  static_cast<LDObject*> (new LDQuad);
+
+						obj->setColor (maincolor);
+
+						for (int i = 0; i < obj->vertices(); ++i)
+							obj->setVertex (i, verts[i]);
+
+						objs << obj;
+					} break;
+				}
+			}
+		} break;
+
+		case ECircleMode:
+		{
+			const int segs = g_lores, divs = g_lores; // TODO: make customizable
+			double dist0 = getCircleDrawDist (0),
+				dist1 = getCircleDrawDist (1);
+			LDDocument* refFile = null;
+			Matrix transform;
+			bool circleOrDisc = false;
+
+			if (dist1 < dist0)
+				std::swap<double> (dist0, dist1);
+
+			if (dist0 == dist1)
+			{
+				// If the radii are the same, there's no ring space to fill. Use a circle.
+				refFile = ::getDocument ("4-4edge.dat");
+				transform = getCircleDrawMatrix (dist0);
+				circleOrDisc = true;
+			}
+			elif (dist0 == 0 || dist1 == 0)
+			{
+				// If either radii is 0, use a disc.
+				refFile = ::getDocument ("4-4disc.dat");
+				transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1);
+				circleOrDisc = true;
+			}
+			elif (g_RingFinder.findRings (dist0, dist1))
+			{
+				// The ring finder found a solution, use that. Add the component rings to the file.
+				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 = ::getDocument (radialFileName (::Ring, g_lores, g_lores, cmp.num))) == null)
+					{
+						refFile = generatePrimitive (::Ring, g_lores, g_lores, cmp.num);
+						refFile->setImplicit (false);
+					}
+
+					LDSubfile* ref = new LDSubfile;
+					ref->setFileInfo (refFile);
+					ref->setTransform (getCircleDrawMatrix (cmp.scale));
+					ref->setPosition (m_drawedVerts[0]);
+					ref->setColor (maincolor);
+					objs << ref;
+				}
+			}
+			else
+			{
+				// Ring finder failed, last resort: draw the ring with quads
+				QList<QLineF> c0, c1;
+				Axis relX, relY, relZ;
+				getRelativeAxes (relX, relY);
+				relZ = (Axis) (3 - relX - relY);
+				double x0 = m_drawedVerts[0][relX],
+					y0 = m_drawedVerts[0][relY];
+
+				Vertex templ;
+				templ[relX] = x0;
+				templ[relY] = y0;
+				templ[relZ] = getDepthValue();
+
+				// Calculate circle coords
+				makeCircle (segs, divs, dist0, c0);
+				makeCircle (segs, divs, dist1, c1);
+
+				for (int i = 0; i < segs; ++i)
+				{
+					Vertex v0, v1, v2, v3;
+					v0 = v1 = v2 = v3 = templ;
+					v0[relX] += c0[i].x1();
+					v0[relY] += c0[i].y1();
+					v1[relX] += c0[i].x2();
+					v1[relY] += c0[i].y2();
+					v2[relX] += c1[i].x2();
+					v2[relY] += c1[i].y2();
+					v3[relX] += c1[i].x1();
+					v3[relY] += c1[i].y1();
+
+					LDQuad* q = new LDQuad (v0, v1, v2, v3);
+					q->setColor (maincolor);
+
+					// Ensure the quads always are BFC-front towards the camera
+					if (camera() % 3 <= 0)
+						q->invert();
+
+					objs << q;
+				}
+			}
+
+			if (circleOrDisc)
+			{
+				LDSubfile* ref = new LDSubfile;
+				ref->setFileInfo (refFile);
+				ref->setTransform (transform);
+				ref->setPosition (m_drawedVerts[0]);
+				ref->setColor (maincolor);
+				objs << ref;
+			}
+		} break;
+
+		case ESelectMode:
+		{
+			// this shouldn't happen
+			assert (false);
+			return;
+		} break;
+	}
+
+	if (objs.size() > 0)
+	{
+		for (LDObject* obj : objs)
+		{
+			document()->addObject (obj);
+			compileObject (obj);
+		}
+
+		g_win->refresh();
+		g_win->endAction();
+	}
+
+	m_drawedVerts.clear();
+	m_rectdraw = false;
+}
+
+// =============================================================================
+//
+double GLRenderer::getCircleDrawDist (int pos) const
+{
+	assert (m_drawedVerts.size() >= pos + 1);
+	const Vertex& v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : m_hoverpos;
+	Axis relX, relY;
+	getRelativeAxes (relX, relY);
+
+	const double dx = m_drawedVerts[0][relX] - v1[relX];
+	const double dy = m_drawedVerts[0][relY] - v1[relY];
+	return sqrt ((dx * dx) + (dy * dy));
+}
+
+// =============================================================================
+//
+void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const
+{
+	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
+	relX = cam->axisX;
+	relY = cam->axisY;
+}
+
+// =============================================================================
+//
+static QList<Vertex> getVertices (LDObject* obj)
+{
+	QList<Vertex> verts;
+
+	if (obj->vertices() >= 2)
+	{
+		for (int i = 0; i < obj->vertices(); ++i)
+			verts << obj->vertex (i);
+	} elif (obj->type() == LDObject::ESubfile)
+	{
+		LDSubfile* ref = static_cast<LDSubfile*> (obj);
+		LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline);
+
+		for (LDObject* obj : objs)
+		{
+			verts << getVertices (obj);
+			obj->destroy();
+		}
+	}
+
+	return verts;
+}
+
+// =============================================================================
+//
+void GLRenderer::compileObject (LDObject* obj)
+{
+	deleteLists (obj);
+
+	for (const GL::ListType listType : g_glListTypes)
+	{
+		if (isDrawOnly() && listType != GL::NormalList)
+			continue;
+
+		GLuint list = glGenLists (1);
+		glNewList (list, GL_COMPILE);
+
+		obj->glLists[listType] = list;
+		compileList (obj, listType);
+
+		glEndList();
+	}
+
+	// Mark in known vertices of this object
+	QList<Vertex> verts = getVertices (obj);
+	m_knownVerts << verts;
+	removeDuplicates (m_knownVerts);
+
+	obj->setGLInit (true);
+}
+
+// =============================================================================
+//
+uchar* GLRenderer::getScreencap (int& w, int& h)
+{
+	w = m_width;
+	h = m_height;
+	uchar* cap = new uchar[4 * w * h];
+
+	m_screencap = true;
+	update();
+	m_screencap = false;
+
+	// Capture the pixels
+	glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap);
+
+	return cap;
+}
+
+// =============================================================================
+//
+void GLRenderer::slot_toolTipTimer()
+{
+	// We come here if the cursor has stayed in one place for longer than a
+	// a second. Check if we're holding it over a camera icon - if so, draw
+	// a tooltip.
+for (CameraIcon & icon : m_cameraIcons)
+	{
+		if (icon.destRect.contains (m_pos))
+		{
+			m_toolTipCamera = icon.cam;
+			m_drawToolTip = true;
+			update();
+			break;
+		}
+	}
+}
+
+// =============================================================================
+//
+void GLRenderer::deleteLists (LDObject* obj)
+{
+	// Delete the lists but only if they have been initialized
+	if (!obj->isGLInit())
+		return;
+
+	for (const GL::ListType listType : g_glListTypes)
+		glDeleteLists (obj->glLists[listType], 1);
+
+	obj->setGLInit (false);
+}
+
+// =============================================================================
+//
+Axis GLRenderer::getCameraAxis (bool y, GLRenderer::EFixedCamera camid)
+{
+	if (camid == (GL::EFixedCamera) - 1)
+		camid = m_camera;
+
+	const LDFixedCameraInfo* cam = &g_FixedCameras[camid];
+	return (y) ? cam->axisY : cam->axisX;
+}
+
+// =============================================================================
+//
+bool GLRenderer::setupOverlay (EFixedCamera cam, QString file, int x, int y, int w, int h)
+{
+	QImage* img = new QImage (QImage (file).convertToFormat (QImage::Format_ARGB32));
+	LDGLOverlay& info = getOverlay (cam);
+
+	if (img->isNull())
+	{
+		critical (tr ("Failed to load overlay image!"));
+		delete img;
+		return false;
+	}
+
+	delete info.img; // delete the old image
+
+	info.fname = file;
+	info.lw = w;
+	info.lh = h;
+	info.ox = x;
+	info.oy = y;
+	info.img = img;
+
+	if (info.lw == 0)
+		info.lw = (info.lh * img->width()) / img->height();
+	elif (info.lh == 0)
+		info.lh = (info.lw * img->height()) / img->width();
+
+	const Axis x2d = getCameraAxis (false, cam),
+		y2d = getCameraAxis (true, cam);
+	const double negXFac = g_FixedCameras[cam].negX ? -1 : 1,
+		negYFac = g_FixedCameras[cam].negY ? -1 : 1;
+
+	info.v0 = info.v1 = g_origin;
+	info.v0[x2d] = - (info.ox * info.lw * negXFac) / img->width();
+	info.v0[y2d] = (info.oy * info.lh * negYFac) / img->height();
+	info.v1[x2d] = info.v0[x2d] + info.lw;
+	info.v1[y2d] = info.v0[y2d] + info.lh;
+
+	// Set alpha of all pixels to 0.5
+	for (long i = 0; i < img->width(); ++i)
+		for (long j = 0; j < img->height(); ++j)
+		{
+			uint32 pixel = img->pixel (i, j);
+			img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF));
+		}
+
+	updateOverlayObjects();
+	return true;
+}
+
+// =============================================================================
+//
+void GLRenderer::clearOverlay()
+{
+	if (camera() == EFreeCamera)
+		return;
+
+	LDGLOverlay& info = currentDocumentData().overlays[camera()];
+	delete info.img;
+	info.img = null;
+
+	updateOverlayObjects();
+}
+
+// =============================================================================
+//
+void GLRenderer::setDepthValue (double depth)
+{
+	assert (camera() < EFreeCamera);
+	currentDocumentData().depthValues[camera()] = depth;
+}
+
+// =============================================================================
+//
+double GLRenderer::getDepthValue() const
+{
+	assert (camera() < EFreeCamera);
+	return currentDocumentData().depthValues[camera()];
+}
+
+// =============================================================================
+//
+const char* GLRenderer::getCameraName() const
+{
+	return g_CameraNames[camera()];
+}
+
+// =============================================================================
+//
+LDGLOverlay& GLRenderer::getOverlay (int newcam)
+{
+	return currentDocumentData().overlays[newcam];
+}
+
+// =============================================================================
+//
+void GLRenderer::zoomNotch (bool inward)
+{
+	if (zoom() > 15)
+		zoom() *= inward ? 0.833f : 1.2f;
+	else
+		zoom() += inward ? -1.2f : 1.2f;
+}
+
+// =============================================================================
+//
+void GLRenderer::zoomToFit()
+{
+	if (document() == null || m_width == -1 || m_height == -1)
+	{
+		zoom() = 30.0f;
+		return;
+	}
+
+	bool lastfilled = false;
+	bool firstrun = true;
+	const uint32 white = 0xFFFFFFFF;
+	bool inward = true;
+	const int w = m_width, h = m_height;
+	int runaway = 50;
+
+	glClearColor (1.0, 1.0, 1.0, 1.0);
+	glDisable (GL_DITHER);
+
+	// Use the pick list while drawing the scene, this way we can tell whether borders
+	// are background or not.
+	setPicking (true);
+
+	while (--runaway)
+	{
+		if (zoom() > 10000.0 || zoom() < 0.0)
+		{
+			// Obviously, there's nothing to draw if we get here.
+			// Default to 30.0f and break out.
+			zoom() = 30.0;
+			break;
+		}
+
+		zoomNotch (inward);
+
+		uchar* cap = new uchar[4 * w * h];
+		drawGLScene();
+		glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap);
+		uint32* imgdata = reinterpret_cast<uint32*> (cap);
+		bool filled = false;
+
+		// Check the top and bottom rows
+		for (int i = 0; i < w; ++i)
+		{
+			if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white)
+			{
+				filled = true;
+				goto endOfLoop;
+			}
+		}
+
+		// Left and right edges
+		for (int i = 0; i < h; ++i)
+		{
+			if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white)
+			{
+				filled = true;
+				goto endOfLoop;
+			}
+		}
+
+endOfLoop:
+
+		delete[] cap;
+
+		if (firstrun)
+		{
+			// If this is the first run, we don't know enough to determine
+			// whether the zoom was to fit, so we mark in our knowledge so
+			// far and start over.
+			inward = !filled;
+			firstrun = false;
+		}
+		else
+		{
+			// If this run filled the screen and the last one did not, the
+			// last run had ideal zoom - zoom a bit back and we should reach it.
+			if (filled && !lastfilled)
+			{
+				zoomNotch (false);
+				break;
+			}
+
+			// If this run did not fill the screen and the last one did, we've
+			// now reached ideal zoom so we're done here.
+			if (!filled && lastfilled)
+				break;
+
+			inward = !filled;
+		}
+
+		lastfilled = filled;
+	}
+
+	setBackground();
+	setPicking (false);
+}
+
+// =============================================================================
+//
+void GLRenderer::zoomAllToFit()
+{
+	EFixedCamera oldcam = camera();
+
+	for (int i = 0; i < 7; ++i)
+	{
+		setCamera ((EFixedCamera) i);
+		zoomToFit();
+	}
+
+	setCamera (oldcam);
+}
+
+// =============================================================================
+//
+void GLRenderer::updateRectVerts()
+{
+	if (!m_rectdraw)
+		return;
+
+	if (m_drawedVerts.isEmpty())
+	{
+		for (int i = 0; i < 4; ++i)
+			m_rectverts[i] = m_hoverpos;
+
+		return;
+	}
+
+	Vertex v0 = m_drawedVerts[0],
+		   v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : m_hoverpos;
+
+	const Axis ax = getCameraAxis (false),
+			   ay = getCameraAxis (true),
+			   az = (Axis) (3 - ax - ay);
+
+	for (int i = 0; i < 4; ++i)
+		m_rectverts[i][az] = getDepthValue();
+
+	m_rectverts[0][ax] = v0[ax];
+	m_rectverts[0][ay] = v0[ay];
+	m_rectverts[1][ax] = v1[ax];
+	m_rectverts[1][ay] = v0[ay];
+	m_rectverts[2][ax] = v1[ax];
+	m_rectverts[2][ay] = v1[ay];
+	m_rectverts[3][ax] = v0[ax];
+	m_rectverts[3][ay] = v1[ay];
+}
+
+// =============================================================================
+//
+void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev)
+{
+	if (!(ev->buttons() & Qt::LeftButton) || editMode() != ESelectMode)
+		return;
+
+	pick (ev->x(), ev->y());
+
+	if (selection().isEmpty())
+		return;
+
+	LDObject* obj = selection().first();
+	AddObjectDialog::staticDialog (obj->type(), obj);
+	g_win->endAction();
+	ev->accept();
+}
+
+// =============================================================================
+//
+LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam)
+{
+	LDOverlay* ovlobj = null;
+
+	for (LDObject* obj : document()->objects())
+	{
+		if (obj->type() == LDObject::EOverlay && static_cast<LDOverlay*> (obj)->camera() == cam)
+		{
+			ovlobj = static_cast<LDOverlay*> (obj);
+			break;
+		}
+	}
+
+	return ovlobj;
+}
+
+// =============================================================================
+//
+// Read in overlays from the current file and update overlay info accordingly.
+//
+void GLRenderer::initOverlaysFromObjects()
+{
+	for (EFixedCamera cam : g_Cameras)
+	{
+		if (cam == EFreeCamera)
+			continue;
+
+		LDGLOverlay& meta = currentDocumentData().overlays[cam];
+		LDOverlay* ovlobj = findOverlayObject (cam);
+
+		if (!ovlobj && meta.img)
+		{
+			delete meta.img;
+			meta.img = null;
+		}
+		elif (ovlobj && (!meta.img || meta.fname != ovlobj->fileName()))
+			setupOverlay (cam, ovlobj->fileName(), ovlobj->x(),
+				ovlobj->y(), ovlobj->width(), ovlobj->height());
+	}
+}
+
+// =============================================================================
+//
+void GLRenderer::updateOverlayObjects()
+{
+	for (EFixedCamera cam : g_Cameras)
+	{
+		if (cam == EFreeCamera)
+			continue;
+
+		LDGLOverlay& meta = currentDocumentData().overlays[cam];
+		LDOverlay* ovlobj = findOverlayObject (cam);
+
+		if (!meta.img && ovlobj)
+		{
+			// If this is the last overlay image, we need to remove the empty space after it as well.
+			LDObject* nextobj = ovlobj->next();
+
+			if (nextobj && nextobj->type() == LDObject::EEmpty)
+				nextobj->destroy();
+
+			// If the overlay object was there and the overlay itself is
+			// not, remove the object.
+			ovlobj->destroy();
+		} elif (meta.img && !ovlobj)
+		{
+			// Inverse case: image is there but the overlay object is
+			// not, thus create the object.
+			ovlobj = new LDOverlay;
+
+			// Find a suitable position to place this object. We want to place
+			// this into the header, which is everything up to the first scemantic
+			// object. If we find another overlay object, place this object after
+			// the last one found. Otherwise, place it before the first schemantic
+			// object and put an empty object after it (though don't do this if
+			// there was no schemantic elements at all)
+			int i, lastOverlay = -1;
+			bool found = false;
+
+			for (i = 0; i < document()->getObjectCount(); ++i)
+			{
+				LDObject* obj = document()->getObject (i);
+
+				if (obj->isScemantic())
+				{
+					found = true;
+					break;
+				}
+
+				if (obj->type() == LDObject::EOverlay)
+					lastOverlay = i;
+			}
+
+			if (lastOverlay != -1)
+				document()->insertObj (lastOverlay + 1, ovlobj);
+			else
+			{
+				document()->insertObj (i, ovlobj);
+
+				if (found)
+					document()->insertObj (i + 1, new LDEmpty);
+			}
+		}
+
+		if (meta.img && ovlobj)
+		{
+			ovlobj->setCamera (cam);
+			ovlobj->setFileName (meta.fname);
+			ovlobj->setX (meta.ox);
+			ovlobj->setY (meta.oy);
+			ovlobj->setWidth (meta.lw);
+			ovlobj->setHeight (meta.lh);
+		}
+	}
+
+	if (g_win->R() == this)
+		g_win->refresh();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glRenderer.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,311 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QGLWidget>
+#include "main.h"
+#include "macros.h"
+#include "ldObject.h"
+#include "ldDocument.h"
+
+class MessageManager;
+class QDialogButtonBox;
+class RadioGroup;
+class QDoubleSpinBox;
+class QSpinBox;
+class QLineEdit;
+class QTimer;
+
+enum EditMode
+{
+	ESelectMode,
+	EDrawMode,
+	ECircleMode,
+};
+
+// Meta for overlays
+struct LDGLOverlay
+{
+	Vertex			v0,
+					v1;
+	int				ox,
+					oy;
+	double			lw,
+					lh;
+	QString			fname;
+	QImage*			img;
+};
+
+struct LDFixedCameraInfo
+{
+	const char		glrotate[3];
+	const Axis		axisX,
+					axisY;
+	const bool		negX,
+					negY;
+};
+
+// =============================================================================
+// Document-specific data
+//
+struct LDGLData
+{
+	double			rotX,
+					rotY,
+					rotZ,
+					panX[7],
+					panY[7],
+					zoom[7];
+	double			depthValues[6];
+	LDGLOverlay		overlays[6];
+	bool			init;
+
+	LDGLData()
+	{
+		for (int i = 0; i < 6; ++i)
+		{
+			overlays[i].img = null;
+			depthValues[i] = 0.0f;
+		}
+
+		init = false;
+	}
+};
+
+// =============================================================================
+// The main renderer object, draws the brick on the screen, manages the camera
+// and selection picking. The instance of GLRenderer is accessible as
+// g_win->R()
+//
+class GLRenderer : public QGLWidget
+{
+	public:
+		enum EFixedCamera
+		{
+			ETopCamera,
+			EFrontCamera,
+			ELeftCamera,
+			EBottomCamera,
+			EBackCamera,
+			ERightCamera,
+			EFreeCamera
+		};
+
+		enum ListType
+		{
+			NormalList,
+			PickList,
+			BFCFrontList,
+			BFCBackList
+		};
+
+		// CameraIcon::img is a heap-allocated QPixmap because otherwise it gets
+		// initialized before program gets to main() and constructs a QApplication
+		// and Qt doesn't like that.
+		struct CameraIcon
+		{
+			QPixmap*			img;
+			QRect				srcRect,
+								destRect,
+								selRect;
+			EFixedCamera	cam;
+		};
+
+		Q_OBJECT
+		PROPERTY (public,	bool,				isDrawOnly,	setDrawOnly,	STOCK_WRITE)
+		PROPERTY (public,	MessageManager*,	messageLog, setMessageLog,	STOCK_WRITE)
+		PROPERTY (private,	bool,				isPicking,	setPicking,		STOCK_WRITE)
+		PROPERTY (public,	LDDocument*,		document,	setDocument,	CUSTOM_WRITE)
+		PROPERTY (public,	EditMode,			editMode,	setEditMode,	CUSTOM_WRITE)
+
+	public:
+		GLRenderer (QWidget* parent = null);
+		~GLRenderer();
+
+		inline EFixedCamera camera() const
+		{
+			return m_camera;
+		}
+
+		void           clearOverlay();
+		void           compileObject (LDObject* obj);
+		void           compileAllObjects();
+		void           drawGLScene();
+		void           endDraw (bool accept);
+		Axis           getCameraAxis (bool y, EFixedCamera camid = (EFixedCamera) - 1);
+		const char*    getCameraName() const;
+		double         getDepthValue() const;
+		QColor         getMainColor();
+		LDGLOverlay&   getOverlay (int newcam);
+		uchar*         getScreencap (int& w, int& h);
+		void           hardRefresh();
+		void           initGLData();
+		void           initOverlaysFromObjects();
+		void           refresh();
+		void           resetAngles();
+		void           resetAllAngles();
+		void           setBackground();
+		void           setCamera (const EFixedCamera cam);
+		void           setDepthValue (double depth);
+		bool           setupOverlay (EFixedCamera cam, QString file, int x, int y, int w, int h);
+		void           updateOverlayObjects();
+		void           zoomNotch (bool inward);
+		void           zoomToFit();
+		void           zoomAllToFit();
+
+		static void    deleteLists (LDObject* obj);
+
+	protected:
+		void           contextMenuEvent (QContextMenuEvent* ev);
+		void           initializeGL();
+		void           keyPressEvent (QKeyEvent* ev);
+		void           keyReleaseEvent (QKeyEvent* ev);
+		void           leaveEvent (QEvent* ev);
+		void           mouseDoubleClickEvent (QMouseEvent* ev);
+		void           mousePressEvent (QMouseEvent* ev);
+		void           mouseMoveEvent (QMouseEvent* ev);
+		void           mouseReleaseEvent (QMouseEvent* ev);
+		void           paintEvent (QPaintEvent* ev);
+		void           resizeGL (int w, int h);
+		void           wheelEvent (QWheelEvent* ev);
+
+	private:
+		CameraIcon					m_cameraIcons[7];
+		QTimer*						m_toolTipTimer;
+		Qt::MouseButtons			m_lastButtons;
+		Qt::KeyboardModifiers		m_keymods;
+		Vertex						m_hoverpos;
+		double						m_virtWidth,
+									m_virtHeight;
+		bool						m_darkbg,
+									m_rangepick,
+									m_addpick,
+									m_drawToolTip,
+									m_screencap,
+									m_panning;
+		QPoint						m_pos,
+									m_globalpos,
+									m_rangeStart;
+		QPen						m_thickBorderPen,
+									m_thinBorderPen;
+		EFixedCamera				m_camera,
+									m_toolTipCamera;
+		GLuint						m_axeslist;
+		int							m_width,
+									m_height,
+									m_totalmove;
+		QList<Vertex>				m_drawedVerts;
+		bool						m_rectdraw;
+		Vertex						m_rectverts[4];
+		QColor						m_bgcolor;
+		QList<Vertex>				m_knownVerts;
+
+		void           addDrawnVertex (Vertex m_hoverpos);
+		LDOverlay*     findOverlayObject (EFixedCamera cam);
+		void           updateRectVerts();
+		void           getRelativeAxes (Axis& relX, Axis& relY) const;
+		Matrix         getCircleDrawMatrix (double scale);
+		void           drawBlip (QPainter& paint, QPoint pos) const;
+
+		// Compute geometry for camera icons
+		void           calcCameraIcons();
+
+		// How large is the circle we're drawing right now?
+		double         getCircleDrawDist (int pos) const;
+
+		// Clamps an angle to [0, 360]
+		void           clampAngle (double& angle) const;
+
+		// Compile one of the lists of an object
+		void           compileList (LDObject* obj, const ListType list);
+
+		// Sub-routine for object compiling
+		void           compileSubObject (LDObject* obj, const GLenum gltype);
+
+		// Compile a single vertex to a list
+		void           compileVertex (const Vertex& vrt);
+
+		// Convert a 2D point to a 3D point
+		Vertex         coordconv2_3 (const QPoint& pos2d, bool snap) const;
+
+		// Convert a 3D point to a 2D point
+		QPoint         coordconv3_2 (const Vertex& pos3d) const;
+
+		// Perform object selection
+		void           pick (int mouseX, int mouseY);
+
+		// Set the color to an object list
+		void           setObjectColor (LDObject* obj, const ListType list);
+
+		LDGLData& currentDocumentData() const
+		{
+			return *document()->getGLData();
+		}
+
+		// Get a rotation value
+		inline double& rot (Axis ax)
+		{
+			return
+				(ax == X) ? currentDocumentData().rotX :
+				(ax == Y) ? currentDocumentData().rotY :
+				            currentDocumentData().rotZ;
+		}
+
+		// Get a panning value
+		inline double& pan (Axis ax)
+		{
+			return (ax == X) ? currentDocumentData().panX[camera()] :
+				currentDocumentData().panY[camera()];
+		}
+
+		// Same except const (can be used in const methods)
+		inline const double& pan (Axis ax) const
+		{
+			return (ax == X) ? currentDocumentData().panX[camera()] :
+				currentDocumentData().panY[camera()];
+		}
+
+		// Get the zoom value
+		inline double& zoom()
+		{
+			return currentDocumentData().zoom[camera()];
+		}
+
+		template<typename... Args>
+		inline QString format (QString fmtstr, Args... args)
+		{
+			return ::format (fmtstr, args...);
+		}
+
+	private slots:
+		void           slot_toolTipTimer();
+};
+
+// Alias for short namespaces
+typedef GLRenderer GL;
+
+static const GLRenderer::ListType g_glListTypes[] =
+{
+	GL::NormalList,
+	GL::PickList,
+	GL::BFCFrontList,
+	GL::BFCBackList,
+};
+
+extern const GL::EFixedCamera g_Cameras[7];
+extern const char* g_CameraNames[7];
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldConfig.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,215 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QFile>
+#include "ldDocument.h"
+#include "ldConfig.h"
+#include "mainWindow.h"
+#include "miscallenous.h"
+#include "colors.h"
+
+// =============================================================================
+//
+// Helper function for parseLDConfig
+//
+static bool parseLDConfigTag (LDConfigParser& pars, char const* tag, QString& val)
+{
+	int pos;
+
+	// Try find the token and get its position
+	if (!pars.findToken (pos, tag, 1))
+		return false;
+
+	// Get the token after it and store it into val
+	return pars.getToken (val, pos + 1);
+}
+
+// =============================================================================
+//
+void parseLDConfig()
+{
+	QFile* fp = openLDrawFile ("LDConfig.ldr", false);
+
+	if (!fp)
+	{
+		critical (QObject::tr ("Unable to open LDConfig.ldr for parsing."));
+		return;
+	}
+
+	// Read in the lines
+	while (fp->atEnd() == false)
+	{
+		QString line = QString::fromUtf8 (fp->readLine());
+
+		if (line.isEmpty() || line[0] != '0')
+			continue; // empty or illogical
+
+		line.remove ('\r');
+		line.remove ('\n');
+
+		// Parse the line
+		LDConfigParser pars (line, ' ');
+
+		int code = 0, alpha = 255;
+		QString name, facename, edgename, valuestr;
+
+		// Check 0 !COLOUR, parse the name
+		if (!pars.tokenCompare (0, "0") || !pars.tokenCompare (1, "!COLOUR") || !pars.getToken (name, 2))
+			continue;
+
+		// Replace underscores in the name with spaces for readability
+		name.replace ("_", " ");
+
+		// Get the CODE tag
+		if (!parseLDConfigTag (pars, "CODE", valuestr))
+			continue;
+
+		if (!numeric (valuestr))
+			continue; // not a number
+
+		// Ensure that the code is within [0 - 511]
+		bool ok;
+		code = valuestr.toShort (&ok);
+
+		if (!ok || code < 0 || code >= 512)
+			continue;
+
+		// VALUE and EDGE tags
+		if (!parseLDConfigTag (pars, "VALUE", facename) || !parseLDConfigTag (pars, "EDGE", edgename))
+			continue;
+
+		// Ensure that our colors are correct
+		QColor faceColor (facename),
+			edgeColor (edgename);
+
+		if (!faceColor.isValid() || !edgeColor.isValid())
+			continue;
+
+		// Parse alpha if given.
+		if (parseLDConfigTag (pars, "ALPHA", valuestr))
+			alpha = clamp (valuestr.toInt(), 0, 255);
+
+		LDColor* col = new LDColor;
+		col->name = name;
+		col->faceColor = faceColor;
+		col->edgeColor = edgeColor;
+		col->hexcode = facename;
+		col->faceColor.setAlpha (alpha);
+		col->index = code;
+		setColor (code, col);
+	}
+
+	fp->close();
+	fp->deleteLater();
+}
+
+// =============================================================================
+//
+LDConfigParser::LDConfigParser (QString inText, char sep)
+{
+	m_tokens = inText.split (sep, QString::SkipEmptyParts);
+	m_pos = -1;
+}
+
+// =============================================================================
+//
+bool LDConfigParser::isAtBeginning()
+{
+	return m_pos == -1;
+}
+
+// =============================================================================
+//
+bool LDConfigParser::isAtEnd()
+{
+	return m_pos == m_tokens.size() - 1;
+}
+
+// =============================================================================
+//
+bool LDConfigParser::getToken (QString& val, const int pos)
+{
+	if (pos >= m_tokens.size())
+		return false;
+
+	val = m_tokens[pos];
+	return true;
+}
+
+// =============================================================================
+//
+bool LDConfigParser::getNextToken (QString& val)
+{
+	return getToken (val, ++m_pos);
+}
+
+// =============================================================================
+//
+bool LDConfigParser::peekNextToken (QString& val)
+{
+	return getToken (val, m_pos + 1);
+}
+
+// =============================================================================
+//
+bool LDConfigParser::findToken (int& result, char const* needle, int args)
+{
+	for (int i = 0; i < (m_tokens.size() - args); ++i)
+	{
+		if (m_tokens[i] == needle)
+		{
+			result = i;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+// =============================================================================
+//
+void LDConfigParser::rewind()
+{
+	m_pos = -1;
+}
+
+// =============================================================================
+//
+void LDConfigParser::seek (int amount, bool rel)
+{
+	m_pos = (rel ? m_pos : 0) + amount;
+}
+
+// =============================================================================
+//
+int LDConfigParser::getSize()
+{
+	return m_tokens.size();
+}
+
+// =============================================================================
+//
+bool LDConfigParser::tokenCompare (int inPos, const char* sOther)
+{
+	QString tok;
+
+	if (!getToken (tok, inPos))
+		return false;
+
+	return (tok == sOther);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldConfig.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,53 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include "basics.h"
+#include <QStringList>
+
+// ============================================================================
+//
+// String parsing utility for parsing ldconfig.ldr
+//
+class LDConfigParser
+{
+	public:
+		LDConfigParser (QString inText, char sep);
+
+		bool isAtEnd();
+		bool isAtBeginning();
+		bool getNextToken (QString& val);
+		bool peekNextToken (QString& val);
+		bool getToken (QString& val, const int pos);
+		bool findToken (int& result, char const* needle, int args);
+		int getSize();
+		void rewind();
+		void seek (int amount, bool rel);
+		bool tokenCompare (int inPos, const char* sOther);
+
+		inline QString operator[] (const int idx)
+		{
+			return m_tokens[idx];
+		}
+
+	private:
+		QStringList m_tokens;
+		int m_pos;
+};
+
+void parseLDConfig();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldDocument.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,1437 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QMessageBox>
+#include <QFileDialog>
+#include <QDir>
+#include <QApplication>
+#include "main.h"
+#include "configuration.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "editHistory.h"
+#include "dialogs.h"
+#include "glRenderer.h"
+#include "misc/invokeLater.h"
+
+cfg (String, io_ldpath, "");
+cfg (List, io_recentfiles, {});
+extern_cfg (String, net_downloadpath);
+extern_cfg (Bool, gl_logostuds);
+
+static bool g_loadingMainFile = false;
+static const int g_maxRecentFiles = 10;
+static bool g_aborted = false;
+static LDDocumentPointer g_logoedStud = null;
+static LDDocumentPointer g_logoedStud2 = null;
+
+LDDocument* LDDocument::m_curdoc = null;
+
+const QStringList g_specialSubdirectories ({ "s", "48", "8" });
+
+// =============================================================================
+//
+namespace LDPaths
+{
+	static QString pathError;
+
+	struct
+	{
+		QString LDConfigPath;
+		QString partsPath, primsPath;
+	} pathInfo;
+
+	void initPaths()
+	{
+		if (!tryConfigure (io_ldpath))
+		{
+			LDrawPathDialog dlg (false);
+
+			if (!dlg.exec())
+				exit (0);
+
+			io_ldpath = dlg.filename();
+		}
+	}
+
+	bool tryConfigure (QString path)
+	{
+		QDir dir;
+
+		if (!dir.cd (path))
+		{
+			pathError = "Directory does not exist.";
+			return false;
+		}
+
+		QStringList mustHave = { "LDConfig.ldr", "parts", "p" };
+		QStringList contents = dir.entryList (mustHave);
+
+		if (contents.size() != mustHave.size())
+		{
+			pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/.";
+			return false;
+		}
+
+		pathInfo.partsPath = format ("%1" DIRSLASH "parts", path);
+		pathInfo.LDConfigPath = format ("%1" DIRSLASH "LDConfig.ldr", path);
+		pathInfo.primsPath = format ("%1" DIRSLASH "p", path);
+
+		return true;
+	}
+
+	// Accessors
+	QString getError()
+	{
+		return pathError;
+	}
+
+	QString ldconfig()
+	{
+		return pathInfo.LDConfigPath;
+	}
+
+	QString prims()
+	{
+		return pathInfo.primsPath;
+	}
+
+	QString parts()
+	{
+		return pathInfo.partsPath;
+	}
+}
+
+// =============================================================================
+//
+LDDocument::LDDocument() :
+	m_gldata (new LDGLData)
+{
+	setImplicit (true);
+	setSavePosition (-1);
+	setTabIndex (-1);
+	setHistory (new History);
+	history()->setDocument (this);
+}
+
+// =============================================================================
+//
+LDDocument::~LDDocument()
+{
+	// Remove this file from the list of files. This MUST be done FIRST, otherwise
+	// a ton of other functions will think this file is still valid when it is not!
+	g_loadedFiles.removeOne (this);
+
+	m_history->setIgnoring (true);
+
+	// Clear everything from the model
+	for (LDObject* obj : objects())
+		obj->destroy();
+
+	// Clear the cache as well
+	for (LDObject* obj : cache())
+		obj->destroy();
+
+	delete m_history;
+	delete m_gldata;
+
+	// If we just closed the current file, we need to set the current
+	// file as something else.
+	if (this == getCurrentDocument())
+	{
+		bool found = false;
+
+		// Try find an explicitly loaded file - if we can't find one,
+		// we need to create a new file to switch to.
+		for (LDDocument* file : g_loadedFiles)
+		{
+			if (!file->isImplicit())
+			{
+				LDDocument::setCurrent (file);
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+			newFile();
+	}
+
+	if (this == g_logoedStud)
+		g_logoedStud = null;
+	elif (this == g_logoedStud2)
+		g_logoedStud2 = null;
+
+	g_win->updateDocumentList();
+	print ("Closed %1", name());
+}
+
+// =============================================================================
+//
+LDDocument* findDocument (QString name)
+{
+	for (LDDocument * file : g_loadedFiles)
+		if (!file->name().isEmpty() && file->name() == name)
+			return file;
+
+	return null;
+}
+
+// =============================================================================
+//
+QString dirname (QString path)
+{
+	long lastpos = path.lastIndexOf (DIRSLASH);
+
+	if (lastpos > 0)
+		return path.left (lastpos);
+
+#ifndef _WIN32
+	if (path[0] == DIRSLASH_CHAR)
+		return DIRSLASH;
+#endif // _WIN32
+
+	return "";
+}
+
+// =============================================================================
+//
+QString basename (QString path)
+{
+	long lastpos = path.lastIndexOf (DIRSLASH);
+
+	if (lastpos != -1)
+		return path.mid (lastpos + 1);
+
+	return path;
+}
+
+// =============================================================================
+//
+static QString findLDrawFilePath (QString relpath, bool subdirs)
+{
+	QString fullPath;
+
+	// LDraw models use Windows-style path separators. If we're not on Windows,
+	// replace the path separator now before opening any files. Qt expects
+	// forward-slashes as directory separators.
+#ifndef WIN32
+	relpath.replace ("\\", "/");
+#endif // WIN32
+
+	// Try find it relative to other currently open documents. We want a file
+	// in the immediate vicinity of a current model to override stock LDraw stuff.
+	QString reltop = basename (dirname (relpath));
+
+	for (LDDocument* doc : g_loadedFiles)
+	{
+		if (doc->fullPath().isEmpty())
+			continue;
+
+		QString partpath = format ("%1/%2", dirname (doc->fullPath()), relpath);
+		QFile f (partpath);
+
+		if (f.exists())
+		{
+			// ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48
+			QString proptop = basename (dirname (partpath));
+
+			bool bogus = false;
+
+			for (QString s : g_specialSubdirectories)
+			{
+				if ((proptop == s && reltop != s) || (reltop == s && proptop != s))
+				{
+					bogus = true;
+					break;
+				}
+			}
+
+			if (!bogus)
+				return partpath;
+		}
+	}
+
+	if (QFile::exists (relpath))
+		return relpath;
+
+	// Try with just the LDraw path first
+	fullPath = format ("%1" DIRSLASH "%2", io_ldpath, relpath);
+
+	if (QFile::exists (fullPath))
+		return fullPath;
+
+	if (subdirs)
+	{
+		// Look in sub-directories: parts and p. Also look in net_downloadpath, since that's
+		// where we download parts from the PT to.
+		for (const QString& topdir : QList<QString> ({ io_ldpath, net_downloadpath }))
+		{
+			for (const QString& subdir : QList<QString> ({ "parts", "p" }))
+			{
+				fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath);
+
+				if (QFile::exists (fullPath))
+					return fullPath;
+			}
+		}
+	}
+
+	// Did not find the file.
+	return "";
+}
+
+QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer)
+{
+	print ("Opening %1...\n", relpath);
+	QString path = findLDrawFilePath (relpath, subdirs);
+
+	if (pathpointer != null)
+		*pathpointer = path;
+
+	if (path.isEmpty())
+		return null;
+
+	QFile* fp = new QFile (path);
+
+	if (fp->open (QIODevice::ReadOnly))
+		return fp;
+
+	fp->deleteLater();
+	return null;
+}
+
+// =============================================================================
+//
+void LDFileLoader::start()
+{
+	setDone (false);
+	setProgress (0);
+	setAborted (false);
+
+	if (isOnForeground())
+	{
+		g_aborted = false;
+
+		// Show a progress dialog if we're loading the main ldDocument.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 (lines().size());
+		dlg->setModal (true);
+		dlg->show();
+
+		// Connect the loader in so we can show updates
+		connect (this, SIGNAL (workDone()), dlg, SLOT (accept()));
+		connect (dlg, SIGNAL (rejected()), this, SLOT (abort()));
+	}
+	else
+		dlg = null;
+
+	// Begin working
+	work (0);
+}
+
+// =============================================================================
+//
+void LDFileLoader::work (int i)
+{
+	// User wishes to abort, so stop here now.
+	if (isAborted())
+	{
+		for (LDObject* obj : m_objects)
+			obj->destroy();
+
+		m_objects.clear();
+		setDone (true);
+		return;
+	}
+
+	// Parse up to 300 lines per iteration
+	int max = i + 300;
+
+	for (; i < max && i < (int) lines().size(); ++i)
+	{
+		QString line = lines()[i];
+
+		// Trim the trailing newline
+		QChar c;
+
+		while (line.endsWith ("\n") || line.endsWith ("\r"))
+			line.chop (1);
+
+		LDObject* obj = parseLine (line);
+
+		// Check for parse errors and warn about tthem
+		if (obj->type() == LDObject::EError)
+		{
+			print ("Couldn't parse line #%1: %2", progress() + 1, static_cast<LDError*> (obj)->reason());
+
+			if (warnings() != null)
+				(*warnings())++;
+		}
+
+		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) lines().size()) - 1)
+	{
+		emit workDone();
+		setDone (true);
+		return;
+	}
+
+	// Otherwise, continue, by recursing back.
+	if (!isDone())
+	{
+		// If we have a dialog to show progress output to, we cannot just call
+		// work() again immediately as the dialog needs some processor cycles as
+		// well. Thus, take a detour through the event loop by using the
+		// meta-object system.
+		//
+		// This terminates the loop here and control goes back to the function
+		// which called the file loader. It will keep processing the event loop
+		// until we're ready (see loadFileContents), thus the event loop will
+		// eventually catch the invokation we throw here and send us back. Though
+		// it's not technically recursion anymore, more like a for loop. :P
+		if (isOnForeground())
+			QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i));
+		else
+			work (i);
+	}
+}
+
+// =============================================================================
+//
+void LDFileLoader::abort()
+{
+	setAborted (true);
+
+	if (isOnForeground())
+		g_aborted = true;
+}
+
+// =============================================================================
+//
+LDObjectList loadFileContents (QFile* fp, int* numWarnings, bool* ok)
+{
+	QStringList lines;
+	LDObjectList objs;
+
+	if (numWarnings)
+		*numWarnings = 0;
+
+	// Read in the lines
+	while (fp->atEnd() == false)
+		lines << QString::fromUtf8 (fp->readLine());
+
+	LDFileLoader* loader = new LDFileLoader;
+	loader->setWarnings (numWarnings);
+	loader->setLines (lines);
+	loader->setOnForeground (g_loadingMainFile);
+	loader->start();
+
+	// After start() returns, if the loader isn't done yet, it's delaying
+	// its next iteration through the event loop. We need to catch this here
+	// by telling the event loop to tick, which will tick the file loader again.
+	// We keep doing this until the file loader is ready.
+	while (loader->isDone() == false)
+		qApp->processEvents();
+
+	// If we wanted the success value, supply that now
+	if (ok)
+		*ok = !loader->isAborted();
+
+	objs = loader->objects();
+	return objs;
+}
+
+// =============================================================================
+//
+LDDocument* openDocument (QString path, bool search)
+{
+	// Convert the file name to lowercase since some parts contain uppercase
+	// file names. I'll assume here that the library will always use lowercase
+	// file names for the actual parts..
+	QFile* fp;
+	QString fullpath;
+
+	if (search)
+		fp = openLDrawFile (path.toLower(), true, &fullpath);
+	else
+	{
+		fp = new QFile (path);
+		fullpath = path;
+
+		if (!fp->open (QIODevice::ReadOnly))
+		{
+			delete fp;
+			return null;
+		}
+	}
+
+	if (!fp)
+		return null;
+
+	LDDocument* load = new LDDocument;
+	load->setFullPath (fullpath);
+	load->setName (LDDocument::shortenName (load->fullPath()));
+	dprint ("name: %1 (%2)", load->name(), load->fullPath());
+	g_loadedFiles << load;
+
+	// Don't take the file loading as actual edits to the file
+	load->history()->setIgnoring (true);
+
+	int numWarnings;
+	bool ok;
+	LDObjectList objs = loadFileContents (fp, &numWarnings, &ok);
+	fp->close();
+	fp->deleteLater();
+
+	if (!ok)
+	{
+		g_loadedFiles.removeOne (load);
+		delete load;
+		return null;
+	}
+
+	load->addObjects (objs);
+
+	if (g_loadingMainFile)
+	{
+		LDDocument::setCurrent (load);
+		g_win->R()->setDocument (load);
+		print (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings);
+	}
+
+	load->history()->setIgnoring (false);
+	return load;
+}
+
+// =============================================================================
+//
+bool LDDocument::isSafeToClose()
+{
+	typedef QMessageBox msgbox;
+	setlocale (LC_ALL, "C");
+
+	// If we have unsaved changes, warn and give the option of saving.
+	if (hasUnsavedChanges())
+	{
+		QString message = format (tr ("There are unsaved changes to %1. Should it be saved?"),
+			(name().length() > 0) ? name() : tr ("<anonymous>"));
+
+		int button = msgbox::question (g_win, tr ("Unsaved Changes"), message,
+			(msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel);
+
+		switch (button)
+		{
+			case msgbox::Yes:
+			{
+				// If we don't have a file path yet, we have to ask the user for one.
+				if (name().length() == 0)
+				{
+					QString newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"),
+						getCurrentDocument()->name(), tr ("LDraw files (*.dat *.ldr)"));
+
+					if (newpath.length() == 0)
+						return false;
+
+					setName (newpath);
+				}
+
+				if (!save())
+				{
+					message = format (tr ("Failed to save %1 (%2)\nDo you still want to close?"),
+						name(), strerror (errno));
+
+					if (msgbox::critical (g_win, tr ("Save Failure"), message,
+						(msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No)
+					{
+						return false;
+					}
+				}
+			} break;
+
+			case msgbox::Cancel:
+				return false;
+
+			default:
+				break;
+		}
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+void closeAll()
+{
+	// Remove all loaded files and the objects they contain
+	QList<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()->setDocument (f);
+	g_win->doFullRefresh();
+	g_win->updateTitle();
+	g_win->updateActions();
+}
+
+// =============================================================================
+//
+void addRecentFile (QString path)
+{
+	auto& rfiles = io_recentfiles;
+	int idx = rfiles.indexOf (path);
+
+	// If this file already is in the list, pop it out.
+	if (idx != -1)
+	{
+		if (rfiles.size() == 1)
+			return; // only recent file - abort and do nothing
+
+		// Pop it out.
+		rfiles.removeAt (idx);
+	}
+
+	// If there's too many recent files, drop one out.
+	while (rfiles.size() > (g_maxRecentFiles - 1))
+		rfiles.removeAt (0);
+
+	// Add the file
+	rfiles << path;
+
+	Config::save();
+	g_win->updateRecentFilesMenu();
+}
+
+// =============================================================================
+// Open an LDraw file and set it as the main model
+// =============================================================================
+void openMainFile (QString path)
+{
+	g_loadingMainFile = true;
+
+	// If there's already a file with the same name, this file must replace it.
+	LDDocument* documentToReplace = null;
+	QString shortName = LDDocument::shortenName (path);
+
+	for (LDDocument* doc : g_loadedFiles)
+	{
+		if (doc->name() == shortName)
+		{
+			documentToReplace = doc;
+			break;
+		}
+	}
+
+	// We cannot open this file if the document this would replace is not
+	// safe to close.
+	if (documentToReplace != null && documentToReplace->isSafeToClose() == false)
+	{
+		g_loadingMainFile = false;
+		return;
+	}
+
+	LDDocument* file = openDocument (path, false);
+
+	if (!file)
+	{
+		// Loading failed, thus drop down to a new file since we
+		// closed everything prior.
+		newFile();
+
+		if (!g_aborted)
+		{
+			// Tell the user loading failed.
+			setlocale (LC_ALL, "C");
+			critical (format (QObject::tr ("Failed to open %1: %2"), path, strerror (errno)));
+		}
+
+		g_loadingMainFile = false;
+		return;
+	}
+
+	file->setImplicit (false);
+
+	// Replace references to the old file with the new file.
+	if (documentToReplace != null)
+	{
+		for (LDDocumentPointer* ptr : documentToReplace->references())
+		{	dprint ("ptr: %1 (%2)\n",
+				ptr, ptr->pointer() ? ptr->pointer()->name() : "<null>");
+
+			*ptr = file;
+		}
+
+		assert (documentToReplace->references().isEmpty());
+		delete documentToReplace;
+	}
+
+	// If we have an anonymous, unchanged file open as the only open file
+	// (aside of the one we just opened), close it now.
+	LDDocument::closeInitialFile();
+
+	// Rebuild the object tree view now.
+	LDDocument::setCurrent (file);
+	g_win->doFullRefresh();
+
+	// Add it to the recent files list.
+	addRecentFile (path);
+	g_loadingMainFile = false;
+}
+
+// =============================================================================
+//
+bool LDDocument::save (QString savepath)
+{
+	if (!savepath.length())
+		savepath = fullPath();
+
+	QFile f (savepath);
+
+	if (!f.open (QIODevice::WriteOnly))
+		return false;
+
+	// If the second object in the list holds the file name, update that now.
+	// Only do this if the file is explicitly open.
+	LDObject* nameObject = getObject (1);
+
+	if (!isImplicit() && nameObject != null && nameObject->type() == LDObject::EComment)
+	{
+		LDComment* nameComment = static_cast<LDComment*> (nameObject);
+
+		if (nameComment->text().left (6) == "Name: ")
+		{
+			QString newname = shortenName (savepath);
+			nameComment->setText (format ("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 : objects())
+		f.write ((obj->asText() + "\r\n").toUtf8());
+
+	// File is saved, now clean up.
+	f.close();
+
+	// We have successfully saved, update the save position now.
+	setSavePosition (history()->position());
+	setFullPath (savepath);
+	setName (shortenName (savepath));
+
+	g_win->updateDocumentListItem (this);
+	g_win->updateTitle();
+	return true;
+}
+
+// =============================================================================
+//
+class LDParseError : public std::exception
+{
+	PROPERTY (private, QString,	error,	setError,	STOCK_WRITE)
+	PROPERTY (private, QString,	line,	setLine,	STOCK_WRITE)
+
+	public:
+		LDParseError (QString line, QString a) :
+			m_error (a),
+			m_line (line) {}
+
+		const char* what() const throw()
+		{
+			return qPrintable (error());
+		}
+};
+
+// =============================================================================
+//
+void checkTokenCount (QString line, const QStringList& tokens, int num)
+{
+	if (tokens.size() != num)
+		throw LDParseError (line, format ("Bad amount of tokens, expected %1, got %2", num, tokens.size()));
+}
+
+// =============================================================================
+//
+void checkTokenNumbers (QString line, const QStringList& tokens, int min, int max)
+{
+	bool ok;
+
+	// Check scientific notation, e.g. 7.99361e-15
+	QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+");
+
+	for (int i = min; i <= max; ++i)
+	{
+		tokens[i].toDouble (&ok);
+
+		if (!ok && !scient.exactMatch (tokens[i]))
+			throw LDParseError (line, format ("Token #%1 was `%2`, expected a number (matched length: %3)", (i + 1), tokens[i], scient.matchedLength()));
+	}
+}
+
+// =============================================================================
+//
+static Vertex parseVertex (QStringList& s, const int n)
+{
+	Vertex v;
+
+	for_axes (ax)
+		v[ax] = s[n + ax].toDouble();
+
+	return v;
+}
+
+// =============================================================================
+// This is the LDraw code parser function. It takes in a string containing LDraw
+// code and returns the object parsed from it. parseLine never returns null,
+// the object will be LDError if it could not be parsed properly.
+// =============================================================================
+LDObject* parseLine (QString line)
+{
+	try
+	{
+		QStringList tokens = line.split (" ", QString::SkipEmptyParts);
+
+		if (tokens.size() <= 0)
+		{
+			// Line was empty, or only consisted of whitespace
+			return new LDEmpty;
+		}
+
+		if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false)
+			throw LDParseError (line, "Illogical line code");
+
+		int num = tokens[0][0].digitValue();
+
+		switch (num)
+		{
+			case 0:
+			{
+				// Comment
+				QString comm = line.mid (line.indexOf ("0") + 1).simplified();
+
+				// Handle BFC statements
+				if (tokens.size() > 2 && tokens[1] == "BFC")
+				{
+					for (int i = 0; i < LDBFC::NumStatements; ++i)
+						if (comm == format ("BFC %1", LDBFC::k_statementStrings [i]))
+							return new LDBFC ( (LDBFC::Statement) i);
+
+					// MLCAD is notorious for stuffing these statements in parts it
+					// creates. The above block only handles valid statements, so we
+					// need to handle MLCAD-style invertnext, clip and noclip separately.
+					struct
+					{
+						QString			a;
+						LDBFC::Statement	b;
+					} BFCData[] =
+					{
+						{ "INVERTNEXT", LDBFC::InvertNext },
+						{ "NOCLIP", LDBFC::NoClip },
+						{ "CLIP", LDBFC::Clip }
+					};
+
+					for (const auto& i : BFCData)
+						if (comm == "BFC CERTIFY " + i.a)
+							return new LDBFC (i.b);
+				}
+
+				if (tokens.size() > 2 && tokens[1] == "!LDFORGE")
+				{
+					// Handle LDForge-specific types, they're embedded into comments too
+					if (tokens[2] == "VERTEX")
+					{
+						// Vertex (0 !LDFORGE VERTEX)
+						checkTokenCount (line, tokens, 7);
+						checkTokenNumbers (line, tokens, 3, 6);
+
+						LDVertex* obj = new LDVertex;
+						obj->setColor (tokens[3].toLong());
+
+						for_axes (ax)
+							obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6
+
+						return obj;
+					} elif (tokens[2] == "OVERLAY")
+					{
+						checkTokenCount (line, tokens, 9);;
+						checkTokenNumbers (line, tokens, 5, 8);
+
+						LDOverlay* obj = new LDOverlay;
+						obj->setFileName (tokens[3]);
+						obj->setCamera (tokens[4].toLong());
+						obj->setX (tokens[5].toLong());
+						obj->setY (tokens[6].toLong());
+						obj->setWidth (tokens[7].toLong());
+						obj->setHeight (tokens[8].toLong());
+						return obj;
+					}
+				}
+
+				// Just a regular comment:
+				LDComment* obj = new LDComment;
+				obj->setText (comm);
+				return obj;
+			}
+
+			case 1:
+			{
+				// Subfile
+				checkTokenCount (line, tokens, 15);
+				checkTokenNumbers (line, tokens, 1, 13);
+
+				// Try open the file. Disable g_loadingMainFile temporarily since we're
+				// not loading the main file now, but the subfile in question.
+				bool tmp = g_loadingMainFile;
+				g_loadingMainFile = false;
+				LDDocument* load = getDocument (tokens[14]);
+				g_loadingMainFile = tmp;
+
+				// If we cannot open the file, mark it an error. Note we cannot use LDParseError
+				// here because the error object needs the document reference.
+				if (!load)
+				{
+					LDError* obj = new LDError (line, format ("Could not open %1", tokens[14]));
+					obj->setFileReferenced (tokens[14]);
+					return obj;
+				}
+
+				LDSubfile* obj = new LDSubfile;
+				obj->setColor (tokens[1].toLong());
+				obj->setPosition (parseVertex (tokens, 2));  // 2 - 4
+
+				Matrix transform;
+
+				for (int i = 0; i < 9; ++i)
+					transform[i] = tokens[i + 5].toDouble(); // 5 - 13
+
+				obj->setTransform (transform);
+				obj->setFileInfo (load);
+				return obj;
+			}
+
+			case 2:
+			{
+				checkTokenCount (line, tokens, 8);
+				checkTokenNumbers (line, tokens, 1, 7);
+
+				// Line
+				LDLine* obj = new LDLine;
+				obj->setColor (tokens[1].toLong());
+
+				for (int i = 0; i < 2; ++i)
+					obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 7
+
+				return obj;
+			}
+
+			case 3:
+			{
+				checkTokenCount (line, tokens, 11);
+				checkTokenNumbers (line, tokens, 1, 10);
+
+				// Triangle
+				LDTriangle* obj = new LDTriangle;
+				obj->setColor (tokens[1].toLong());
+
+				for (int i = 0; i < 3; ++i)
+					obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 10
+
+				return obj;
+			}
+
+			case 4:
+			case 5:
+			{
+				checkTokenCount (line, tokens, 14);
+				checkTokenNumbers (line, tokens, 1, 13);
+
+				// Quadrilateral / Conditional line
+				LDObject* obj;
+
+				if (num == 4)
+					obj = new LDQuad;
+				else
+					obj = new LDCondLine;
+
+				obj->setColor (tokens[1].toLong());
+
+				for (int i = 0; i < 4; ++i)
+					obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 13
+
+				return obj;
+			}
+
+			default: // Strange line we couldn't parse
+				throw LDError (line, "Unknown line code number");
+		}
+	}
+	catch (LDParseError& e)
+	{
+		return new LDError (e.line(), e.error());
+	}
+}
+
+// =============================================================================
+//
+LDDocument* getDocument (QString filename)
+{
+	// Try find the file in the list of loaded files
+	LDDocument* doc = findDocument (filename);
+
+	// If it's not loaded, try open it
+	if (!doc)
+		doc = openDocument (filename, true);
+
+	return doc;
+}
+
+// =============================================================================
+//
+void reloadAllSubfiles()
+{
+	if (!getCurrentDocument())
+		return;
+
+	g_loadedFiles.clear();
+	g_loadedFiles << getCurrentDocument();
+
+	// Go through all objects in the current file and reload the subfiles
+	for (LDObject* obj : getCurrentDocument()->objects())
+	{
+		if (obj->type() == LDObject::ESubfile)
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			LDDocument* fileInfo = getDocument (ref->fileInfo()->name());
+
+			if (fileInfo)
+				ref->setFileInfo (fileInfo);
+			else
+				ref->replace (new LDError (ref->asText(), format ("Could not open %1", ref->fileInfo()->name())));
+		}
+
+		// Reparse gibberish files. It could be that they are invalid because
+		// of loading errors. Circumstances may be different now.
+		if (obj->type() == LDObject::EError)
+			obj->replace (parseLine (static_cast<LDError*> (obj)->contents()));
+	}
+}
+
+// =============================================================================
+//
+int LDDocument::addObject (LDObject* obj)
+{
+	history()->add (new AddHistory (objects().size(), obj));
+	m_objects << obj;
+
+	if (obj->type() == LDObject::EVertex)
+		m_vertices << obj;
+
+#ifdef DEBUG
+	if (!isImplicit())
+		dprint ("Added object #%1 (%2)\n", obj->id(), obj->typeName());
+#endif
+
+	obj->setDocument (this);
+	return getObjectCount() - 1;
+}
+
+// =============================================================================
+//
+void LDDocument::addObjects (const LDObjectList objs)
+{
+	for (LDObject* obj : objs)
+		if (obj)
+			addObject (obj);
+}
+
+// =============================================================================
+//
+void LDDocument::insertObj (int pos, LDObject* obj)
+{
+	history()->add (new AddHistory (pos, obj));
+	m_objects.insert (pos, obj);
+	obj->setDocument (this);
+
+#ifdef DEBUG
+	if (!isImplicit())
+		dprint ("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos);
+#endif
+}
+
+// =============================================================================
+//
+void LDDocument::forgetObject (LDObject* obj)
+{
+	int idx = obj->lineNumber();
+	obj->unselect();
+	assert (m_objects[idx] == obj);
+
+	if (!history()->isIgnoring())
+		history()->add (new DelHistory (idx, obj));
+
+	m_objects.removeAt (idx);
+	obj->setDocument (null);
+}
+
+// =============================================================================
+//
+bool safeToCloseAll()
+{
+	for (LDDocument* f : g_loadedFiles)
+		if (!f->isSafeToClose())
+			return false;
+
+	return true;
+}
+
+// =============================================================================
+//
+void LDDocument::setObject (int idx, LDObject* obj)
+{
+	assert (idx >= 0 && idx < m_objects.size());
+
+	// Mark this change to history
+	if (!m_history->isIgnoring())
+	{
+		QString oldcode = getObject (idx)->asText();
+		QString newcode = obj->asText();
+		*m_history << new EditHistory (idx, oldcode, newcode);
+	}
+
+	m_objects[idx]->unselect();
+	m_objects[idx]->setDocument (null);
+	obj->setDocument (this);
+	m_objects[idx] = obj;
+}
+
+// =============================================================================
+//
+// Close all documents we don't need anymore
+//
+void LDDocument::closeUnused()
+{
+	for (LDDocument* file : g_loadedFiles)
+		if (file->isImplicit() && file->references().isEmpty())
+			delete file;
+}
+
+// =============================================================================
+//
+LDObject* LDDocument::getObject (int pos) const
+{
+	if (m_objects.size() <= pos)
+		return null;
+
+	return m_objects[pos];
+}
+
+// =============================================================================
+//
+int LDDocument::getObjectCount() const
+{
+	return objects().size();
+}
+
+// =============================================================================
+//
+bool LDDocument::hasUnsavedChanges() const
+{
+	return !isImplicit() && history()->position() != savePosition();
+}
+
+// =============================================================================
+//
+QString LDDocument::getDisplayName()
+{
+	if (!name().isEmpty())
+		return name();
+
+	if (!defaultName().isEmpty())
+		return "[" + defaultName() + "]";
+
+	return tr ("<anonymous>");
+}
+
+// =============================================================================
+//
+LDObjectList LDDocument::inlineContents (LDSubfile::InlineFlags flags)
+{
+	// Possibly substitute with logoed studs:
+	// stud.dat -> stud-logo.dat
+	// stud2.dat -> stud-logo2.dat
+	if (gl_logostuds && (flags & LDSubfile::RendererInline))
+	{
+		// Ensure logoed studs are loaded first
+		loadLogoedStuds();
+
+		if (name() == "stud.dat" && g_logoedStud)
+			return g_logoedStud->inlineContents (flags);
+		elif (name() == "stud2.dat" && g_logoedStud2)
+			return g_logoedStud2->inlineContents (flags);
+	}
+
+	LDObjectList objs, objcache;
+
+	bool deep = flags & LDSubfile::DeepInline,
+		 doCache = flags & LDSubfile::CacheInline;
+
+	if (m_needsCache)
+	{
+		m_cache.clear();
+		doCache = true;
+	}
+
+	// If we have this cached, just create a copy of that
+	if (deep && cache().isEmpty() == false)
+	{
+		for (LDObject* obj : cache())
+			objs << obj->createCopy();
+	}
+	else
+	{
+		if (!deep)
+			doCache = false;
+
+		for (LDObject* obj : objects())
+		{
+			// 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->type() == LDObject::ESubfile)
+			{
+				LDSubfile* ref = static_cast<LDSubfile*> (obj);
+
+				// We only want to cache immediate subfiles, so shed the caching
+				// flag when recursing deeper in hierarchy.
+				LDObjectList otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline));
+
+				for (LDObject* otherobj : otherobjs)
+				{
+					// Cache this object, if desired
+					if (doCache)
+						objcache << otherobj->createCopy();
+
+					objs << otherobj;
+				}
+			}
+			else
+			{
+				if (doCache)
+					objcache << obj->createCopy();
+
+				objs << obj->createCopy();
+			}
+		}
+
+		if (doCache)
+			setCache (objcache);
+	}
+
+	return objs;
+}
+
+// =============================================================================
+//
+LDDocument* LDDocument::current()
+{
+	return m_curdoc;
+}
+
+// =============================================================================
+// Sets the given file as the current one on display. At some point in time this
+// was an operation completely unheard of. ;)
+//
+// TODO: f can be temporarily null. This probably should not be the case.
+// =============================================================================
+void LDDocument::setCurrent (LDDocument* f)
+{
+	// Implicit files were loaded for caching purposes and must never be set
+	// current.
+	if (f && f->isImplicit())
+		return;
+
+	m_curdoc = f;
+
+	if (g_win && f)
+	{
+		// A ton of stuff needs to be updated
+		g_win->updateDocumentListItem (f);
+		g_win->buildObjList();
+		g_win->updateTitle();
+		g_win->R()->setDocument (f);
+		g_win->R()->repaint();
+		print ("Changed file to %1", f->getDisplayName());
+	}
+}
+
+// =============================================================================
+//
+int LDDocument::countExplicitFiles()
+{
+	int count = 0;
+
+	for (LDDocument* f : g_loadedFiles)
+		if (f->isImplicit() == false)
+			count++;
+
+	return count;
+}
+
+// =============================================================================
+// This little beauty closes the initial file that was open at first when opening
+// a new file over it.
+// =============================================================================
+void LDDocument::closeInitialFile()
+{
+	if (
+		countExplicitFiles() == 2 &&
+		g_loadedFiles[0]->name().isEmpty() &&
+		g_loadedFiles[1]->name().isEmpty() == false &&
+		!g_loadedFiles[0]->hasUnsavedChanges()
+	)
+		delete g_loadedFiles[0];
+}
+
+// =============================================================================
+//
+void loadLogoedStuds()
+{
+	if (g_logoedStud && g_logoedStud2)
+		return;
+
+	delete g_logoedStud;
+	delete g_logoedStud2;
+
+	g_logoedStud = openDocument ("stud-logo.dat", true);
+	g_logoedStud2 = openDocument ("stud2-logo.dat", true);
+
+	print (LDDocument::tr ("Logoed studs loaded.\n"));
+}
+
+// =============================================================================
+//
+void LDDocument::addToSelection (LDObject* obj) // [protected]
+{
+	if (obj->isSelected())
+		return;
+
+	assert (obj->document() == this);
+	m_sel << obj;
+	obj->setSelected (true);
+}
+
+// =============================================================================
+//
+void LDDocument::removeFromSelection (LDObject* obj) // [protected]
+{
+	if (!obj->isSelected())
+		return;
+
+	assert (obj->document() == this);
+	m_sel.removeOne (obj);
+	obj->setSelected (false);
+}
+
+// =============================================================================
+//
+void LDDocument::clearSelection()
+{
+	for (LDObject* obj : m_sel)
+		removeFromSelection (obj);
+
+	assert (m_sel.isEmpty());
+}
+
+// =============================================================================
+//
+const LDObjectList& LDDocument::getSelection() const
+{
+	return m_sel;
+}
+
+// =============================================================================
+//
+void LDDocument::swapObjects (LDObject* one, LDObject* other)
+{
+	int a = m_objects.indexOf (one);
+	int b = m_objects.indexOf (other);
+	assert (a != b && a != -1 && b != -1);
+	m_objects[b] = one;
+	m_objects[a] = other;
+	addToHistory (new SwapHistory (one->id(), other->id()));
+}
+
+// =============================================================================
+//
+QString LDDocument::shortenName (QString a) // [static]
+{
+	QString shortname = basename (a);
+	QString topdirname = basename (dirname (a));
+
+	if (g_specialSubdirectories.contains (topdirname))
+		shortname.prepend (topdirname + "\\");
+
+	return shortname;
+}
+
+// =============================================================================
+//
+void LDDocument::addReference (LDDocumentPointer* ptr)
+{
+	m_references << ptr;
+}
+
+// =============================================================================
+//
+void LDDocument::removeReference (LDDocumentPointer* ptr)
+{
+	m_references.removeOne (ptr);
+
+	if (references().isEmpty())
+		invokeLater (closeUnused);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldDocument.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,240 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QObject>
+#include "main.h"
+#include "ldObject.h"
+#include "editHistory.h"
+
+class History;
+class OpenProgressDialog;
+class LDDocumentPointer;
+struct LDGLData;
+
+namespace LDPaths
+{
+	void initPaths();
+	bool tryConfigure (QString path);
+
+	QString ldconfig();
+	QString prims();
+	QString parts();
+	QString getError();
+}
+
+// =============================================================================
+//
+// This class stores a document 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
+{
+	public:
+		using ReferenceList = QList<LDDocumentPointer*>;
+
+		Q_OBJECT
+		PROPERTY (public,	QString,		name,			setName,			STOCK_WRITE)
+		PROPERTY (private,	LDObjectList,	objects, 		setObjects,			STOCK_WRITE)
+		PROPERTY (private,	LDObjectList,	cache, 			setCache,			STOCK_WRITE)
+		PROPERTY (private,	History*,		history,		setHistory,			STOCK_WRITE)
+		PROPERTY (private,	LDObjectList,	vertices,		setVertices,		STOCK_WRITE)
+		PROPERTY (private,	ReferenceList,	references,		setReferences,		STOCK_WRITE)
+		PROPERTY (public,	QString,		fullPath,		setFullPath,		STOCK_WRITE)
+		PROPERTY (public,	QString,		defaultName,	setDefaultName,		STOCK_WRITE)
+		PROPERTY (public,	bool,			isImplicit,		setImplicit,		STOCK_WRITE)
+		PROPERTY (public,	long,			savePosition,	setSavePosition,	STOCK_WRITE)
+		PROPERTY (public,	int,			tabIndex,		setTabIndex,		STOCK_WRITE)
+
+	public:
+		LDDocument();
+		~LDDocument();
+
+		int addObject (LDObject* obj); // Adds an object to this file at the end of the file.
+		void addObjects (const LDObjectList objs);
+		void clearSelection();
+		void forgetObject (LDObject* obj); // Deletes the given object from the object chain.
+		QString getDisplayName();
+		const LDObjectList& getSelection() const;
+		bool hasUnsavedChanges() const; // Does this document have unsaved changes?
+		LDObjectList inlineContents (LDSubfile::InlineFlags flags);
+		void insertObj (int pos, LDObject* obj);
+		int getObjectCount() const;
+		LDObject* getObject (int pos) const;
+		bool save (QString path = ""); // Saves this file to disk.
+		void swapObjects (LDObject* one, LDObject* other);
+		bool isSafeToClose(); // Perform safety checks. Do this before closing any files!
+		void setObject (int idx, LDObject* obj);
+		void addReference (LDDocumentPointer* ptr);
+		void removeReference (LDDocumentPointer* ptr);
+
+		inline LDDocument& operator<< (LDObject* obj)
+		{
+			addObject (obj);
+			return *this;
+		}
+
+		inline void addHistoryStep()
+		{
+			history()->addStep();
+		}
+
+		inline void undo()
+		{
+			history()->undo();
+		}
+
+		inline void redo()
+		{
+			history()->redo();
+		}
+
+		inline void clearHistory()
+		{
+			history()->clear();
+		}
+
+		inline void addToHistory (AbstractHistoryEntry* entry)
+		{
+			*history() << entry;
+		}
+
+		static void closeUnused();
+		static LDDocument* current();
+		static void setCurrent (LDDocument* f);
+		static void closeInitialFile();
+		static int countExplicitFiles();
+
+		// Turns a full path into a relative path
+		static QString shortenName (QString a);
+
+	protected:
+		void addToSelection (LDObject* obj);
+		void removeFromSelection (LDObject* obj);
+
+		LDGLData* getGLData()
+		{
+			return m_gldata;
+		}
+
+		friend class LDObject;
+		friend class GLRenderer;
+
+	private:
+		LDObjectList			m_sel;
+		LDGLData*				m_gldata;
+
+		// If set to true, next inline of this document discards the cache and
+		// re-builds it.
+		bool					m_needsCache;
+
+		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 (QString path);
+
+// Finds an OpenFile by name or null if not open
+LDDocument* findDocument (QString name);
+
+// Opens the given file and parses the LDraw code within. Returns a pointer
+// to the opened file or null on error.
+LDDocument* openDocument (QString path, bool search);
+
+// Opens the given file and returns a pointer to it, potentially looking in /parts and /p
+QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer = null);
+
+// 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 (QString 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 (QString filename);
+
+// Re-caches all subfiles.
+void reloadAllSubfiles();
+
+// Is it safe to close all files?
+bool safeToCloseAll();
+
+LDObjectList loadFileContents (QFile* f, int* numWarnings, bool* ok = null);
+
+extern QList<LDDocument*> g_loadedFiles;
+
+inline const LDObjectList& selection()
+{
+	return getCurrentDocument()->getSelection();
+}
+
+void addRecentFile (QString path);
+void loadLogoedStuds();
+QString basename (QString path);
+QString dirname (QString path);
+
+extern QList<LDDocument*> g_loadedFiles; // Vector of all currently opened files.
+
+// =============================================================================
+//
+// LDFileLoader
+//
+// 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,	LDObjectList,	objects,		setObjects,			STOCK_WRITE)
+	PROPERTY (private,	bool,			isDone,			setDone,			STOCK_WRITE)
+	PROPERTY (private,	int,			progress,		setProgress,		STOCK_WRITE)
+	PROPERTY (private,	bool,			isAborted,		setAborted,			STOCK_WRITE)
+	PROPERTY (public,	QStringList,	lines,			setLines,			STOCK_WRITE)
+	PROPERTY (public,	int*,			warnings,		setWarnings,		STOCK_WRITE)
+	PROPERTY (public,	bool,			isOnForeground,	setOnForeground,	STOCK_WRITE)
+
+	public slots:
+		void start();
+		void abort();
+
+	private:
+		OpenProgressDialog* dlg;
+
+	private slots:
+		void work (int i);
+
+	signals:
+		void progressUpdate (int progress);
+		void workDone();
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldObject.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,825 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "main.h"
+#include "ldObject.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "editHistory.h"
+#include "glRenderer.h"
+#include "colors.h"
+
+cfg (String, ld_defaultname, "");
+cfg (String, ld_defaultuser, "");
+cfg (Int, ld_defaultlicense, 0);
+
+// List of all LDObjects
+static LDObjectList g_LDObjects;
+
+// =============================================================================
+// LDObject constructors
+//
+LDObject::LDObject() :
+	m_isHidden (false),
+	m_isSelected (false),
+	m_parent (null),
+	m_document (null),
+	m_isGLInit (false),
+	qObjListEntry (null)
+{
+	memset (m_coords, 0, sizeof m_coords);
+	chooseID();
+	g_LDObjects << this;
+}
+
+// =============================================================================
+//
+void LDObject::chooseID()
+{
+	int32 id = 1; // 0 shalt be null
+
+	for (LDObject* obj : g_LDObjects)
+	{
+		assert (obj != this);
+
+		if (obj->id() >= id)
+			id = obj->id() + 1;
+	}
+
+	setID (id);
+}
+
+// =============================================================================
+//
+void LDObject::setVertexCoord (int i, Axis ax, double value)
+{
+	Vertex v = vertex (i);
+	v[ax] = value;
+	setVertex (i, v);
+}
+
+LDError::LDError() {}
+
+// =============================================================================
+//
+QString LDComment::asText() const
+{
+	return format ("0 %1", text());
+}
+
+// =============================================================================
+//
+QString LDSubfile::asText() const
+{
+	QString val = format ("1 %1 %2 ", color(), position());
+	val += transform().toString();
+	val += ' ';
+	val += fileInfo()->name();
+	return val;
+}
+
+// =============================================================================
+//
+QString LDLine::asText() const
+{
+	QString val = format ("2 %1", color());
+
+	for (int i = 0; i < 2; ++i)
+		val += format (" %1", vertex (i));
+
+	return val;
+}
+
+// =============================================================================
+//
+QString LDTriangle::asText() const
+{
+	QString val = format ("3 %1", color());
+
+	for (int i = 0; i < 3; ++i)
+		val += format (" %1", vertex (i));
+
+	return val;
+}
+
+// =============================================================================
+//
+QString LDQuad::asText() const
+{
+	QString val = format ("4 %1", color());
+
+	for (int i = 0; i < 4; ++i)
+		val += format (" %1", vertex (i));
+
+	return val;
+}
+
+// =============================================================================
+//
+QString LDCondLine::asText() const
+{
+	QString val = format ("5 %1", color());
+
+	// Add the coordinates
+	for (int i = 0; i < 4; ++i)
+		val += format (" %1", vertex (i));
+
+	return val;
+}
+
+// =============================================================================
+//
+QString LDError::asText() const
+{
+	return contents();
+}
+
+// =============================================================================
+//
+QString LDVertex::asText() const
+{
+	return format ("0 !LDFORGE VERTEX %1 %2", color(), pos);
+}
+
+// =============================================================================
+//
+QString LDEmpty::asText() const
+{
+	return "";
+}
+
+// =============================================================================
+//
+const char* LDBFC::k_statementStrings[] =
+{
+	"CERTIFY CCW",
+	"CCW",
+	"CERTIFY CW",
+	"CW",
+	"NOCERTIFY",
+	"INVERTNEXT",
+	"CLIP",
+	"CLIP CCW",
+	"CLIP CW",
+	"NOCLIP",
+};
+
+QString LDBFC::asText() const
+{
+	return format ("0 BFC %1", LDBFC::k_statementStrings[m_statement]);
+}
+
+// =============================================================================
+//
+QList<LDTriangle*> LDQuad::splitToTriangles()
+{
+	// Create the two triangles based on this quadrilateral:
+	// 0---3       0---3    3
+	// |   |       |  /    /|
+	// |   |  ==>  | /    / |
+	// |   |       |/    /  |
+	// 1---2       1    1---2
+	LDTriangle* tri1 = new LDTriangle (vertex (0), vertex (1), vertex (3));
+	LDTriangle* tri2 = new LDTriangle (vertex (1), vertex (2), vertex (3));
+
+	// The triangles also inherit the quad's color
+	tri1->setColor (color());
+	tri2->setColor (color());
+
+	QList<LDTriangle*> triangles;
+	triangles << tri1;
+	triangles << tri2;
+	return triangles;
+}
+
+// =============================================================================
+//
+void LDObject::replace (LDObject* other)
+{
+	long idx = lineNumber();
+	assert (idx != -1);
+
+	// Replace the instance of the old object with the new object
+	document()->setObject (idx, other);
+
+	// Remove the old object
+	destroy();
+}
+
+// =============================================================================
+//
+void LDObject::swap (LDObject* other)
+{
+	assert (document() == other->document());
+	document()->swapObjects (this, other);
+}
+
+// =============================================================================
+//
+LDLine::LDLine (Vertex v1, Vertex v2)
+{
+	setVertex (0, v1);
+	setVertex (1, v2);
+}
+
+// =============================================================================
+//
+LDQuad::LDQuad (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3)
+{
+	setVertex (0, v0);
+	setVertex (1, v1);
+	setVertex (2, v2);
+	setVertex (3, v3);
+}
+
+// =============================================================================
+//
+LDObject::~LDObject() {}
+
+// =============================================================================
+//
+LDSubfile::~LDSubfile() {}
+
+// =============================================================================
+//
+void LDObject::destroy()
+{
+	// If this object was selected, unselect it now
+	if (isSelected())
+		unselect();
+
+	// If this object was associated to a file, remove it off it now
+	if (document())
+		document()->forgetObject (this);
+
+	// Delete the GL lists
+	GL::deleteLists (this);
+
+	// Remove this object from the list of LDObjects
+	g_LDObjects.removeOne (this);
+
+	delete this;
+}
+
+// =============================================================================
+//
+static void transformObject (LDObject* obj, Matrix transform, Vertex pos, int parentcolor)
+{
+	switch (obj->type())
+	{
+		case LDObject::ELine:
+		case LDObject::ECondLine:
+		case LDObject::ETriangle:
+		case LDObject::EQuad:
+
+			for (int i = 0; i < obj->vertices(); ++i)
+			{
+				Vertex v = obj->vertex (i);
+				v.transform (transform, pos);
+				obj->setVertex (i, v);
+			}
+
+			break;
+
+		case LDObject::ESubfile:
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			Matrix newMatrix = transform * ref->transform();
+			Vertex newpos = ref->position();
+
+			newpos.transform (transform, pos);
+			ref->setPosition (newpos);
+			ref->setTransform (newMatrix);
+		}
+		break;
+
+		default:
+			break;
+	}
+
+	if (obj->color() == maincolor)
+		obj->setColor (parentcolor);
+}
+
+// =============================================================================
+//
+LDObjectList LDSubfile::inlineContents (InlineFlags flags)
+{
+	LDObjectList objs = fileInfo()->inlineContents (flags);
+
+	// Transform the objects
+	for (LDObject* obj : objs)
+	{
+		// Set the parent now so we know what inlined the object.
+		obj->setParent (this);
+		transformObject (obj, transform(), position(), color());
+	}
+
+	return objs;
+}
+
+// =============================================================================
+//
+long LDObject::lineNumber() const
+{
+	assert (document() != null);
+
+	for (int i = 0; i < document()->getObjectCount(); ++i)
+		if (document()->getObject (i) == this)
+			return i;
+
+	return -1;
+}
+
+// =============================================================================
+//
+void LDObject::moveObjects (LDObjectList objs, const bool up)
+{
+	if (objs.isEmpty())
+		return;
+
+	// If we move down, we need to iterate the array in reverse order.
+	const long start = up ? 0 : (objs.size() - 1);
+	const long end = up ? objs.size() : -1;
+	const long incr = up ? 1 : -1;
+	LDObjectList objsToCompile;
+	LDDocument* file = objs[0]->document();
+
+	for (long i = start; i != end; i += incr)
+	{
+		LDObject* obj = objs[i];
+
+		const long idx = obj->lineNumber(),
+				   target = idx + (up ? -1 : 1);
+
+		if ((up && idx == 0) || (!up && idx == (long) (file->objects().size() - 1)))
+		{
+			// One of the objects hit the extrema. If this happens, this should be the first
+			// object to be iterated on. Thus, nothing has changed yet and it's safe to just
+			// abort the entire operation.
+			assert (i == start);
+			return;
+		}
+
+		objsToCompile << obj;
+		objsToCompile << file->getObject (target);
+
+		obj->swap (file->getObject (target));
+	}
+
+	removeDuplicates (objsToCompile);
+
+	// The objects need to be recompiled, otherwise their pick lists are left with
+	// the wrong index colors which messes up selection.
+	for (LDObject* obj : objsToCompile)
+		g_win->R()->compileObject (obj);
+}
+
+// =============================================================================
+//
+QString LDObject::typeName (LDObject::Type type)
+{
+	LDObject* obj = LDObject::getDefault (type);
+	QString name = obj->typeName();
+	obj->destroy();
+	return name;
+}
+
+// =============================================================================
+//
+QString LDObject::describeObjects (const LDObjectList& objs)
+{
+	bool firstDetails = true;
+	QString text = "";
+
+	if (objs.isEmpty())
+		return "nothing"; // :)
+
+	for (long i = 0; i < ENumTypes; ++i)
+	{
+		Type objType = (Type) i;
+		int count = 0;
+
+		for (LDObject * obj : objs)
+			if (obj->type() == objType)
+				count++;
+
+		if (count == 0)
+			continue;
+
+		if (!firstDetails)
+			text += ", ";
+
+		QString noun = format ("%1%2", typeName (objType), plural (count));
+
+		// Plural of "vertex" is "vertices", correct that
+		if (objType == EVertex && count != 1)
+			noun = "vertices";
+
+		text += format ("%1 %2", count, noun);
+		firstDetails = false;
+	}
+
+	return text;
+}
+
+// =============================================================================
+//
+LDObject* LDObject::topLevelParent()
+{
+	if (parent() == null)
+		return this;
+
+	LDObject* it = this;
+
+	while (it->parent() != null)
+		it = it->parent();
+
+	return it;
+}
+
+// =============================================================================
+//
+LDObject* LDObject::next() const
+{
+	long idx = lineNumber();
+	assert (idx != -1);
+
+	if (idx == (long) document()->getObjectCount() - 1)
+		return null;
+
+	return document()->getObject (idx + 1);
+}
+
+// =============================================================================
+//
+LDObject* LDObject::previous() const
+{
+	long idx = lineNumber();
+	assert (idx != -1);
+
+	if (idx == 0)
+		return null;
+
+	return document()->getObject (idx - 1);
+}
+
+// =============================================================================
+//
+void LDObject::move (Vertex vect)
+{
+	if (hasMatrix())
+	{
+		LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (this);
+		mo->setPosition (mo->position() + vect);
+	}
+	elif (type() == LDObject::EVertex)
+	{
+		// ugh
+		static_cast<LDVertex*> (this)->pos += vect;
+	}
+	else
+	{
+		for (int i = 0; i < vertices(); ++i)
+			setVertex (i, vertex (i) + vect);
+	}
+}
+
+// =============================================================================
+//
+#define CHECK_FOR_OBJ(N) \
+	if (type == LDObject::E##N) \
+		return new LD##N;
+
+LDObject* LDObject::getDefault (const LDObject::Type type)
+{
+	CHECK_FOR_OBJ (Comment)
+	CHECK_FOR_OBJ (BFC)
+	CHECK_FOR_OBJ (Line)
+	CHECK_FOR_OBJ (CondLine)
+	CHECK_FOR_OBJ (Subfile)
+	CHECK_FOR_OBJ (Triangle)
+	CHECK_FOR_OBJ (Quad)
+	CHECK_FOR_OBJ (Empty)
+	CHECK_FOR_OBJ (BFC)
+	CHECK_FOR_OBJ (Error)
+	CHECK_FOR_OBJ (Vertex)
+	CHECK_FOR_OBJ (Overlay)
+	return null;
+}
+
+// =============================================================================
+//
+void LDObject::invert() {}
+void LDBFC::invert() {}
+void LDEmpty::invert() {}
+void LDComment::invert() {}
+void LDError::invert() {}
+
+// =============================================================================
+//
+void LDTriangle::invert()
+{
+	// Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1.
+	// Thus, we swap 1 and 2.
+	Vertex tmp = vertex (1);
+	setVertex (1, vertex (2));
+	setVertex (2, tmp);
+
+	return;
+}
+
+// =============================================================================
+//
+void LDQuad::invert()
+{
+	// Quad: 0 -> 1 -> 2 -> 3
+	// rev:  0 -> 3 -> 2 -> 1
+	// Thus, we swap 1 and 3.
+	Vertex tmp = vertex (1);
+	setVertex (1, vertex (3));
+	setVertex (3, tmp);
+}
+
+// =============================================================================
+//
+void LDSubfile::invert()
+{
+	// Subfiles are inverted when they're prefixed with
+	// a BFC INVERTNEXT statement. Thus we need to toggle this status.
+	// For flat primitives it's sufficient that the determinant is
+	// flipped but I don't have a method for checking flatness yet.
+	// Food for thought...
+
+	int idx = lineNumber();
+
+	if (idx > 0)
+	{
+		LDBFC* bfc = dynamic_cast<LDBFC*> (previous());
+
+		if (bfc && bfc->statement() == LDBFC::InvertNext)
+		{
+			// This is prefixed with an invertnext, thus remove it.
+			bfc->destroy();
+			return;
+		}
+	}
+
+	// Not inverted, thus prefix it with a new invertnext.
+	LDBFC* bfc = new LDBFC (LDBFC::InvertNext);
+	document()->insertObj (idx, bfc);
+}
+
+// =============================================================================
+//
+static void invertLine (LDObject* line)
+{
+	// For lines, we swap the vertices. I don't think that a
+	// cond-line's control points need to be swapped, do they?
+	Vertex tmp = line->vertex (0);
+	line->setVertex (0, line->vertex (1));
+	line->setVertex (1, tmp);
+}
+
+void LDLine::invert()
+{
+	invertLine (this);
+}
+
+void LDCondLine::invert()
+{
+	invertLine (this);
+}
+
+void LDVertex::invert() {}
+
+// =============================================================================
+//
+LDLine* LDCondLine::demote()
+{
+	LDLine* repl = new LDLine;
+
+	for (int i = 0; i < repl->vertices(); ++i)
+		repl->setVertex (i, vertex (i));
+
+	repl->setColor (color());
+
+	replace (repl);
+	return repl;
+}
+
+// =============================================================================
+//
+LDObject* LDObject::fromID (int id)
+{
+	for (LDObject* obj : g_LDObjects)
+		if (obj->id() == id)
+			return obj;
+
+	return null;
+}
+
+// =============================================================================
+//
+QString LDOverlay::asText() const
+{
+	return format ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6",
+		fileName(), camera(), x(), y(), width(), height());
+}
+
+void LDOverlay::invert() {}
+
+// =============================================================================
+// Hook the set accessors of certain properties to this changeProperty function.
+// It takes care of history management so we can capture low-level changes, this
+// makes history stuff work out of the box.
+//
+template<class T> static void changeProperty (LDObject* obj, T* ptr, const T& val)
+{
+	long idx;
+
+	if (*ptr == val)
+		return;
+
+	if (obj->document() && (idx = obj->lineNumber()) != -1)
+	{
+		QString before = obj->asText();
+		*ptr = val;
+		QString after = obj->asText();
+
+		if (before != after)
+			obj->document()->addToHistory (new EditHistory (idx, before, after));
+	}
+	else
+		*ptr = val;
+}
+
+// =============================================================================
+//
+void LDObject::setColor (const int& val)
+{
+	changeProperty (this, &m_color, val);
+}
+
+// =============================================================================
+//
+const Vertex& LDObject::vertex (int i) const
+{
+	return m_coords[i]->data();
+}
+
+// =============================================================================
+//
+void LDObject::setVertex (int i, const Vertex& vert)
+{
+	changeProperty (this, &m_coords[i], LDSharedVertex::getSharedVertex (vert));
+}
+
+// =============================================================================
+//
+void LDMatrixObject::setPosition (const Vertex& a)
+{
+	changeProperty (linkPointer(), &m_position, LDSharedVertex::getSharedVertex (a));
+}
+
+// =============================================================================
+//
+void LDMatrixObject::setTransform (const Matrix& val)
+{
+	changeProperty (linkPointer(), &m_transform, val);
+}
+
+// =============================================================================
+//
+static QMap<Vertex, LDSharedVertex*> g_sharedVerts;
+
+LDSharedVertex* LDSharedVertex::getSharedVertex (const Vertex& a)
+{
+	auto it = g_sharedVerts.find (a);
+
+	if (it == g_sharedVerts.end())
+	{
+		LDSharedVertex* v = new LDSharedVertex (a);
+		g_sharedVerts[a] = v;
+		return v;
+	}
+
+	return *it;
+}
+
+// =============================================================================
+//
+void LDSharedVertex::addRef (LDObject* a)
+{
+	m_refs << a;
+}
+
+// =============================================================================
+//
+void LDSharedVertex::delRef (LDObject* a)
+{
+	m_refs.removeOne (a);
+
+	if (m_refs.empty())
+	{
+		g_sharedVerts.remove (m_data);
+		delete this;
+	}
+}
+
+// =============================================================================
+//
+void LDObject::select()
+{
+	assert (document() != null);
+	document()->addToSelection (this);
+}
+
+// =============================================================================
+//
+void LDObject::unselect()
+{
+	assert (document() != null);
+	document()->removeFromSelection (this);
+}
+
+// =============================================================================
+//
+QString getLicenseText (int id)
+{
+	switch (id)
+	{
+		case 0:
+			return g_CALicense;
+
+		case 1:
+			return g_nonCALicense;
+
+		case 2:
+			return "";
+	}
+
+	assert (false);
+	return "";
+}
+
+// =============================================================================
+//
+LDObject* LDObject::createCopy() const
+{
+	/*
+	LDObject* copy = clone();
+	copy->setFile (null);
+	copy->setGLInit (false);
+	copy->chooseID();
+	copy->setSelected (false);
+	*/
+
+	/*
+	LDObject* copy = getDefault (getType());
+	copy->setColor (color());
+
+	if (hasMatrix())
+	{
+		LDMatrixObject* copyMo = static_cast<LDMatrixObject*> (copy);
+		const LDMatrixObject* mo = static_cast<const LDMatrixObject*> (this);
+		copyMo->setPosition (mo->getPosition());
+		copyMo->setTransform (mo->transform());
+	}
+	else
+	{
+		for (int i = 0; i < vertices(); ++i)
+			copy->setVertex (getVertex (i));
+	}
+
+	switch (getType())
+	{
+		case Subfile:
+		{
+			LDSubfile* copyRef = static_cast<LDSubfile*> (copy);
+			const LDSubfile* ref = static_cast<const LDSubfile*> (this);
+
+			copyRef->setFileInfo (ref->fileInfo());
+		}
+	}
+	*/
+
+	LDObject* copy = parseLine (asText());
+	return copy;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldObject.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,553 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include "main.h"
+#include "basics.h"
+#include "misc/documentPointer.h"
+
+#define LDOBJ(T)										\
+protected:												\
+	virtual LD##T* clone() override						\
+	{													\
+		return new LD##T (*this);						\
+	}													\
+														\
+public:													\
+	virtual LDObject::Type type() const override		\
+	{													\
+		return LDObject::E##T;							\
+	}													\
+	virtual QString asText() const override;		\
+	virtual void invert() override;
+
+#define LDOBJ_NAME(N)          virtual QString typeName() const override { return #N; }
+#define LDOBJ_VERTICES(V)      virtual int vertices() const override { return V; }
+#define LDOBJ_SETCOLORED(V)    virtual bool isColored() const override { return V; }
+#define LDOBJ_COLORED          LDOBJ_SETCOLORED (true)
+#define LDOBJ_UNCOLORED        LDOBJ_SETCOLORED (false)
+
+#define LDOBJ_CUSTOM_SCEMANTIC virtual bool isScemantic() const override
+#define LDOBJ_SCEMANTIC        LDOBJ_CUSTOM_SCEMANTIC { return true; }
+#define LDOBJ_NON_SCEMANTIC    LDOBJ_CUSTOM_SCEMANTIC { return false; }
+
+#define LDOBJ_SETMATRIX(V)     virtual bool hasMatrix() const override { return V; }
+#define LDOBJ_HAS_MATRIX       LDOBJ_SETMATRIX (true)
+#define LDOBJ_NO_MATRIX        LDOBJ_SETMATRIX (false)
+
+class QListWidgetItem;
+class LDSubfile;
+class LDDocument;
+class LDSharedVertex;
+
+// =============================================================================
+// LDObject
+//
+// Base class object for all object types. Each LDObject represents a single line
+// in the LDraw code file. The virtual method getType returns an enumerator
+// which is a token of the object's type. The object can be casted into
+// sub-classes based on this enumerator.
+// =============================================================================
+class LDObject
+{
+	PROPERTY (public,		bool,			isHidden,		setHidden,		STOCK_WRITE)
+	PROPERTY (public,		bool,			isSelected,		setSelected,	STOCK_WRITE)
+	PROPERTY (public,		LDObject*,		parent,			setParent,		STOCK_WRITE)
+	PROPERTY (public,		LDDocument*,	document,		setDocument,	STOCK_WRITE)
+	PROPERTY (private,		int,			id,				setID,			STOCK_WRITE)
+	PROPERTY (public,		int,			color,			setColor,		CUSTOM_WRITE)
+	PROPERTY (public,		bool,			isGLInit,		setGLInit,		STOCK_WRITE)
+
+	public:
+		// Object type codes.
+		enum Type
+		{
+			ESubfile,        // Object represents a sub-file reference
+			EQuad,           // Object represents a quadrilateral
+			ETriangle,       // Object represents a triangle
+			ELine,           // Object represents a line
+			ECondLine,       // Object represents a conditional line
+			EVertex,         // Object is a vertex, LDForge extension object
+			EBFC,            // Object represents a BFC statement
+			EOverlay,        // Object contains meta-info about an overlay image.
+			EComment,        // Object represents a comment
+			EError,          // Object is the result of failed parsing
+			EEmpty,          // Object represents an empty line
+			EUnidentified,   // Unknown object type (some functions return this; TODO: they probably should not)
+			ENumTypes        // Amount of object types
+		};
+
+		LDObject();
+
+		// Makes a copy of this object
+		LDObject*					createCopy() const;
+
+		// Deletes this object
+		void						destroy();
+
+		// Index (i.e. line number) of this object
+		long						lineNumber() const;
+
+		// Type enumerator of this object
+		virtual Type				type() const = 0;
+
+		// Get a vertex by index
+		const Vertex&				vertex (int i) const;
+
+		// Type name of this object
+		virtual QString				typeName() const = 0;
+
+		// Does this object have a matrix and position? (see LDMatrixObject)
+		virtual bool				hasMatrix() const = 0;
+
+		// Inverts this object (winding is reversed)
+		virtual void				invert() = 0;
+
+		// Is this object colored?
+		virtual bool				isColored() const = 0;
+
+		// Does this object have meaning in the part model?
+		virtual bool				isScemantic() const = 0;
+
+		// Moves this object using the given vertex as a movement List
+		void						move (Vertex vect);
+
+		// Object after this in the current file
+		LDObject*					next() const;
+
+		// Object prior to this in the current file
+		LDObject*					previous() const;
+
+		// This object as LDraw code
+		virtual QString				asText() const = 0;
+
+		// Replace this LDObject with another LDObject. Object is deleted in the process.
+		void						replace (LDObject* other);
+
+		// Selects this object.
+		void						select();
+
+		// Set a vertex to the given value
+		void						setVertex (int i, const Vertex& vert);
+
+		// Set a single coordinate of a vertex
+		void						setVertexCoord (int i, Axis ax, double value);
+
+		// Swap this object with another.
+		void						swap (LDObject* other);
+
+		// What object in the current file ultimately references this?
+		LDObject*					topLevelParent();
+
+		// Removes this object from selection // TODO: rename to deselect?
+		void						unselect();
+
+		// Number of vertices this object has // TODO: rename to getNumVertices
+		virtual int					vertices() const = 0;
+
+		// Get type name by enumerator
+		static QString typeName (LDObject::Type type);
+
+		// Returns a default-constructed LDObject by the given type
+		static LDObject* getDefault (const LDObject::Type type);
+
+		// TODO: move this to LDDocument?
+		static void moveObjects (LDObjectList objs, const bool up);
+
+		// Get a description of a list of LDObjects
+		static QString describeObjects (const LDObjectList& objs);
+		static LDObject* fromID (int id);
+
+		// TODO: make these private!
+		// OpenGL list for this object
+		uint glLists[4];
+
+		// Object list entry for this object
+		QListWidgetItem* qObjListEntry;
+
+	protected:
+		// LDObjects are to be deleted with the deleteSelf() method, not with
+		// operator delete. This is because it seems virtual functions cannot
+		// be properly called from the destructor, thus a normal method must
+		// be used instead. The destructor also doesn't seem to be able to
+		// be private without causing a truckload of problems so it's protected
+		// instead.
+		virtual ~LDObject();
+		void chooseID();
+
+	private:
+		virtual LDObject* clone() = 0;
+		LDSharedVertex*	m_coords[4];
+};
+
+// =============================================================================
+// LDSharedVertex
+//
+// For use as coordinates of LDObjects. Keeps count of references.
+// =============================================================================
+class LDSharedVertex
+{
+	public:
+		inline const Vertex& data() const
+		{
+			return m_data;
+		}
+
+		inline operator const Vertex&() const
+		{
+			return m_data;
+		}
+
+		void addRef (LDObject* a);
+		void delRef (LDObject* a);
+
+		static LDSharedVertex* getSharedVertex (const Vertex& a);
+
+	protected:
+		LDSharedVertex (const Vertex& a) : m_data (a) {}
+
+	private:
+		LDObjectList m_refs;
+		Vertex m_data;
+};
+
+// =============================================================================
+//
+// Common code for objects with matrices. This class is multiple-derived in
+// and thus not used directly other than as a common storage point for matrices
+// and vertices.
+//
+// The link pointer is a pointer to this object's LDObject self - since this is
+// multiple-derived in, static_cast or dynamic_cast won't budge here.
+//
+// In 0.1-alpha, there was a separate 'radial' type which had a position and
+// matrix as well. Even though right now only LDSubfile uses this, I'm keeping
+// this class distinct in case I get new extension ideas. :)
+//
+class LDMatrixObject
+{
+	PROPERTY (public,	LDObject*,	linkPointer,	setLinkPointer,	STOCK_WRITE)
+	PROPERTY (public,	Matrix,		transform,		setTransform,	CUSTOM_WRITE)
+
+	public:
+		LDMatrixObject() :
+			m_position (LDSharedVertex::getSharedVertex (g_origin)) {}
+
+		LDMatrixObject (const Matrix& transform, const Vertex& pos) :
+			m_transform (transform),
+			m_position (LDSharedVertex::getSharedVertex (pos)) {}
+
+		inline const Vertex& position() const
+		{
+			return m_position->data();
+		}
+
+		void setCoordinate (const Axis ax, double value)
+		{
+			Vertex v = position();
+			v[ax] = value;
+			setPosition (v);
+		}
+
+		void setPosition (const Vertex& a);
+
+	private:
+		LDSharedVertex*	m_position;
+};
+
+// =============================================================================
+//
+// Represents a line in the LDraw file that could not be properly parsed. It is
+// represented by a (!) ERROR in the code view. It exists for the purpose of
+// allowing garbage lines be debugged and corrected within LDForge.
+//
+class LDError : public LDObject
+{
+	LDOBJ (Error)
+	LDOBJ_NAME (error)
+	LDOBJ_VERTICES (0)
+	LDOBJ_UNCOLORED
+	LDOBJ_SCEMANTIC
+	LDOBJ_NO_MATRIX
+	PROPERTY (public,	QString,	fileReferenced, setFileReferenced,	STOCK_WRITE)
+	PROPERTY (private,	QString,	contents,		setContents,		STOCK_WRITE)
+	PROPERTY (private,	QString,	reason,			setReason,			STOCK_WRITE)
+
+	public:
+		LDError();
+		LDError (QString contents, QString reason) :
+			m_contents (contents),
+			m_reason (reason) {}
+};
+
+// =============================================================================
+//
+// Represents an empty line in the LDraw code file.
+//
+class LDEmpty : public LDObject
+{
+	LDOBJ (Empty)
+	LDOBJ_NAME (empty)
+	LDOBJ_VERTICES (0)
+	LDOBJ_UNCOLORED
+	LDOBJ_NON_SCEMANTIC
+	LDOBJ_NO_MATRIX
+};
+
+// =============================================================================
+//
+// Represents a code-0 comment in the LDraw code file.
+//
+class LDComment : public LDObject
+{
+	PROPERTY (public, QString, text, setText, STOCK_WRITE)
+	LDOBJ (Comment)
+	LDOBJ_NAME (comment)
+	LDOBJ_VERTICES (0)
+	LDOBJ_UNCOLORED
+	LDOBJ_NON_SCEMANTIC
+	LDOBJ_NO_MATRIX
+
+	public:
+		LDComment() {}
+		LDComment (QString text) : m_text (text) {}
+};
+
+// =============================================================================
+//
+// Represents a 0 BFC statement in the LDraw code. eStatement contains the type
+// of this statement.
+//
+class LDBFC : public LDObject
+{
+	public:
+		enum Statement
+		{
+			CertifyCCW,
+			CCW,
+			CertifyCW,
+			CW,
+			NoCertify,
+			InvertNext,
+			Clip,
+			ClipCCW,
+			ClipCW,
+			NoClip,
+			NumStatements
+		};
+
+		LDOBJ (BFC)
+		LDOBJ_NAME (bfc)
+		LDOBJ_VERTICES (0)
+		LDOBJ_UNCOLORED
+		LDOBJ_CUSTOM_SCEMANTIC { return (statement() == InvertNext); }
+		LDOBJ_NO_MATRIX
+		PROPERTY (public, Statement, statement, setStatement, STOCK_WRITE)
+
+	public:
+		LDBFC() {}
+		LDBFC (const LDBFC::Statement type) :
+			m_statement (type) {}
+
+		// Statement strings
+		static const char* k_statementStrings[];
+};
+
+// =============================================================================
+// LDSubfile
+//
+// Represents a single code-1 subfile reference.
+// =============================================================================
+class LDSubfile : public LDObject, public LDMatrixObject
+{
+	LDOBJ (Subfile)
+	LDOBJ_NAME (subfile)
+	LDOBJ_VERTICES (0)
+	LDOBJ_COLORED
+	LDOBJ_SCEMANTIC
+	LDOBJ_HAS_MATRIX
+	PROPERTY (public, LDDocumentPointer, fileInfo, setFileInfo, STOCK_WRITE)
+
+	public:
+		enum InlineFlag
+		{
+			DeepInline     = (1 << 0),
+			CacheInline    = (1 << 1),
+			RendererInline = (1 << 2),
+			DeepCacheInline = (DeepInline | CacheInline),
+		};
+
+		Q_DECLARE_FLAGS (InlineFlags, InlineFlag)
+
+		LDSubfile()
+		{
+			setLinkPointer (this);
+		}
+
+		// Inlines this subfile. Note that return type is an array of heap-allocated
+		// LDObject copies, they must be deleted manually.
+		LDObjectList inlineContents (InlineFlags flags);
+
+	protected:
+		~LDSubfile();
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS (LDSubfile::InlineFlags)
+
+// =============================================================================
+// LDLine
+//
+// Represents a single code-2 line in the LDraw code file. v0 and v1 are the end
+// points of the line. The line is colored with dColor unless uncolored mode is
+// set.
+// =============================================================================
+class LDLine : public LDObject
+{
+	LDOBJ (Line)
+	LDOBJ_NAME (line)
+	LDOBJ_VERTICES (2)
+	LDOBJ_COLORED
+	LDOBJ_SCEMANTIC
+	LDOBJ_NO_MATRIX
+
+	public:
+		LDLine() {}
+		LDLine (Vertex v1, Vertex v2);
+};
+
+// =============================================================================
+// LDCondLine
+//
+// Represents a single code-5 conditional line. The end-points v0 and v1 are
+// inherited from LDLine, c0 and c1 are the control points of this line.
+// =============================================================================
+class LDCondLine : public LDLine
+{
+	LDOBJ (CondLine)
+	LDOBJ_NAME (condline)
+	LDOBJ_VERTICES (4)
+	LDOBJ_COLORED
+	LDOBJ_SCEMANTIC
+	LDOBJ_NO_MATRIX
+
+	public:
+		LDCondLine() {}
+		LDLine* demote();
+};
+
+// =============================================================================
+// LDTriangle
+//
+// Represents a single code-3 triangle in the LDraw code file. Vertices v0, v1
+// and v2 contain the end-points of this triangle. dColor is the color the
+// triangle is colored with.
+// =============================================================================
+class LDTriangle : public LDObject
+{
+	LDOBJ (Triangle)
+	LDOBJ_NAME (triangle)
+	LDOBJ_VERTICES (3)
+	LDOBJ_COLORED
+	LDOBJ_SCEMANTIC
+	LDOBJ_NO_MATRIX
+
+	public:
+		LDTriangle() {}
+		LDTriangle (Vertex v0, Vertex v1, Vertex v2)
+		{
+			setVertex (0, v0);
+			setVertex (1, v1);
+			setVertex (2, v2);
+		}
+};
+
+// =============================================================================
+// LDQuad
+//
+// Represents a single code-4 quadrilateral. v0, v1, v2 and v3 are the end points
+// of the quad, dColor is the color used for the quad.
+// =============================================================================
+class LDQuad : public LDObject
+{
+	LDOBJ (Quad)
+	LDOBJ_NAME (quad)
+	LDOBJ_VERTICES (4)
+	LDOBJ_COLORED
+	LDOBJ_SCEMANTIC
+	LDOBJ_NO_MATRIX
+
+	public:
+		LDQuad() {}
+		LDQuad (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3);
+
+		// Split this quad into two triangles (note: heap-allocated)
+		QList<LDTriangle*> splitToTriangles();
+};
+
+// =============================================================================
+// LDVertex
+//
+// The vertex is an LDForce-specific extension which represents a single
+// vertex which can be used as a parameter to tools or to store coordinates
+// with. Vertices are a part authoring tool and they should not appear in
+// finished parts.
+// =============================================================================
+class LDVertex : public LDObject
+{
+	LDOBJ (Vertex)
+	LDOBJ_NAME (vertex)
+	LDOBJ_VERTICES (0) // TODO: move pos to vaCoords[0]
+	LDOBJ_COLORED
+	LDOBJ_NON_SCEMANTIC
+	LDOBJ_NO_MATRIX
+
+	public:
+		LDVertex() {}
+
+		Vertex pos;
+};
+
+// =============================================================================
+// LDOverlay
+//
+// Overlay image meta, stored in the header of parts so as to preserve overlay
+// information.
+// =============================================================================
+class LDOverlay : public LDObject
+{
+	LDOBJ (Overlay)
+	LDOBJ_NAME (overlay)
+	LDOBJ_VERTICES (0)
+	LDOBJ_UNCOLORED
+	LDOBJ_NON_SCEMANTIC
+	LDOBJ_NO_MATRIX
+	PROPERTY (public,	int,	 camera,	setCamera,		STOCK_WRITE)
+	PROPERTY (public,	int,	 x,			setX,			STOCK_WRITE)
+	PROPERTY (public,	int,	 y,			setY,			STOCK_WRITE)
+	PROPERTY (public,	int,	 width,		setWidth,		STOCK_WRITE)
+	PROPERTY (public,	int,	 height,	setHeight,		STOCK_WRITE)
+	PROPERTY (public,	QString, fileName,	setFileName,	STOCK_WRITE)
+};
+
+// Other common LDraw stuff
+static const QString g_CALicense ("!LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt");
+static const QString g_nonCALicense ("!LICENSE Not redistributable : see NonCAreadme.txt");
+static const int g_lores = 16;
+static const int g_hires = 48;
+
+QString getLicenseText (int id);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/macros.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,104 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#ifndef __GNUC__
+# define __attribute__(X)
+#endif
+
+// =============================================================================
+//
+#define PROPERTY(ACCESS, TYPE, READ, WRITE, WRITETYPE)			\
+private:														\
+	TYPE m_##READ;												\
+																\
+public:															\
+	inline TYPE const& READ() const								\
+	{															\
+		return m_##READ; 										\
+	}															\
+																\
+ACCESS:															\
+	void WRITE (TYPE const& a) PROPERTY_##WRITETYPE (READ)		\
+
+#define PROPERTY_STOCK_WRITE(READ)								\
+	{															\
+		m_##READ = a;											\
+	}
+
+#define PROPERTY_CUSTOM_WRITE(READ)								\
+	;
+
+// =============================================================================
+//
+#define elif(A) else if (A)
+
+// =============================================================================
+//
+#ifdef WIN32
+# define DIRSLASH "\\"
+# define DIRSLASH_CHAR '\\'
+#else // WIN32
+# define DIRSLASH "/"
+# define DIRSLASH_CHAR '/'
+#endif // WIN32
+
+// =============================================================================
+//
+#ifdef __GNUC__
+#define FUNCNAME __PRETTY_FUNCTION__
+#else
+#define FUNCNAME __func__
+#endif // __GNUC__
+
+// =============================================================================
+//
+#define dvalof(A) dprint ("value of '%1' = %2\n", #A, A)
+
+// =============================================================================
+//
+// Replace assert with a version that shows a GUI dialog if possible.
+// On Windows I just can't get the actual error messages otherwise.
+//
+#undef assert
+
+#ifdef DEBUG
+# define assert(N) { ((N) ? (void) 0 : assertionFailure (__FILE__, __LINE__, FUNCNAME, #N)); }
+#else
+# define assert(N) {}
+#endif // DEBUG
+
+#define for_axes(AX) for (const Axis AX : std::initializer_list<const Axis> ({X, Y, Z}))
+
+// =============================================================================
+#ifdef IN_IDE_PARSER // KDevelop workarounds:
+# error IN_IDE_PARSER is defined (this code is only for KDevelop workarounds)
+# define COMPILE_DATE "14-01-10 10:31:09"
+
+# ifndef va_start
+#  define va_start(va, arg)
+# endif // va_start
+
+# ifndef va_end
+#  define va_end(va)
+# endif // va_end
+
+static const char* __func__ = ""; // Current function name
+typedef void FILE; // :|
+#endif // IN_IDE_PARSER
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,84 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QApplication>
+#include <QMessageBox>
+#include <QAbstractButton>
+#include <QFile>
+#include <QTextStream>
+#include <QDir>
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "configuration.h"
+#include "colors.h"
+#include "basics.h"
+#include "primitives.h"
+#include "glRenderer.h"
+#include "configDialog.h"
+#include "dialogs.h"
+#include "crashCatcher.h"
+
+QList<LDDocument*> g_loadedFiles;
+MainWindow* g_win = null;
+static QString g_versionString, g_fullVersionString;
+
+const Vertex g_origin (0.0f, 0.0f, 0.0f);
+const Matrix g_identity ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
+
+cfg (Bool, firststart, true);
+
+// =============================================================================
+//
+int main (int argc, char* argv[])
+{
+	QApplication app (argc, argv);
+	app.setOrganizationName (APPNAME);
+	app.setApplicationName (APPNAME);
+	initCrashCatcher();
+	LDDocument::setCurrent (null);
+
+	// Load or create the configuration
+	if (!Config::load())
+	{
+		print ("Creating configuration file...\n");
+
+		if (Config::save())
+			print ("Configuration file successfully created.\n");
+		else
+			critical ("Failed to create configuration file!\n");
+	}
+
+	LDPaths::initPaths();
+	initColors();
+	MainWindow* win = new MainWindow;
+	newFile();
+	win->show();
+
+	// If this is the first start, get the user to configuration. Especially point
+	// them to the profile tab, it's the most important form to fill in.
+	if (firststart)
+	{
+		(new ConfigDialog (ConfigDialog::ProfileTab))->exec();
+		firststart = false;
+		Config::save();
+	}
+
+	loadPrimitives();
+	return app.exec();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,42 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// =============================================================================
+// This file is included one way or another in every source file of LDForge.
+// Stuff defined and included here is universally included.
+
+#pragma once
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <QString>
+#include <QTextFormat>
+#include "macros.h"
+#include "version.h"
+#include "configuration.h"
+#include "format.h"
+
+// Null pointer
+static const std::nullptr_t null = nullptr;
+
+void assertionFailure (const char* file, int line, const char* funcname, const char* expr);
+
+// Version string identifier. These are defined in Version.cc.
+const char* versionString();
+const char* fullVersionString();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mainWindow.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,1034 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QGridLayout>
+#include <QMessageBox>
+#include <QEvent>
+#include <QContextMenuEvent>
+#include <QMenuBar>
+#include <QStatusBar>
+#include <QSplitter>
+#include <QListWidget>
+#include <QToolButton>
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QToolBar>
+#include <QProgressBar>
+#include <QLabel>
+#include <QFileDialog>
+#include <QPushButton>
+#include <QCoreApplication>
+#include <QTimer>
+#include <QMetaMethod>
+#include "main.h"
+#include "glRenderer.h"
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "configuration.h"
+#include "miscallenous.h"
+#include "colors.h"
+#include "editHistory.h"
+#include "radioGroup.h"
+#include "addObjectDialog.h"
+#include "messageLog.h"
+#include "configuration.h"
+#include "ui_ldforge.h"
+
+static bool g_isSelectionLocked = false;
+
+cfg (Bool, lv_colorize, true);
+cfg (String, gui_colortoolbar, "16:24:|:4:25:14:27:2:3:11:1:22:|:0:72:71:15");
+cfg (Bool, gui_implicitfiles, false);
+extern_cfg (List,		io_recentfiles);
+extern_cfg (Bool,		gl_axes);
+extern_cfg (String,	gl_maincolor);
+extern_cfg (Float,	gl_maincolor_alpha);
+extern_cfg (Bool,		gl_wireframe);
+extern_cfg (Bool,		gl_colorbfc);
+extern_cfg (Bool,		gl_drawangles);
+
+// =============================================================================
+//
+MainWindow::MainWindow (QWidget* parent, Qt::WindowFlags flags) :
+	QMainWindow (parent, flags)
+{
+	g_win = this;
+	ui = new Ui_LDForgeUI;
+	ui->setupUi (this);
+	m_updatingTabs = false;
+	m_renderer = new GLRenderer (this);
+	m_tabs = new QTabBar;
+	ui->verticalLayout->insertWidget (0, m_tabs);
+
+	// Stuff the renderer into its frame
+	QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame);
+	rendererLayout->addWidget (R());
+
+	connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged()));
+	connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*)));
+	connect (m_tabs, SIGNAL (currentChanged(int)), this, SLOT (changeCurrentFile()));
+
+	// Init message log manager
+	m_msglog = new MessageManager;
+	m_msglog->setRenderer (R());
+	m_renderer->setMessageLog (m_msglog);
+	m_quickColors = quickColorsFromConfig();
+	slot_selectionChanged();
+	setStatusBar (new QStatusBar);
+
+	// Make certain actions checkable
+	ui->actionAxes->setChecked (gl_axes);
+	ui->actionWireframe->setChecked (gl_wireframe);
+	ui->actionBFCView->setChecked (gl_colorbfc);
+	updateGridToolBar();
+	updateEditModeActions();
+	updateRecentFilesMenu();
+	updateColorToolbar();
+	updateTitle();
+	updateActionShortcuts();
+
+	setMinimumSize (300, 200);
+
+	connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup()));
+
+	// Connect all actions
+	for (QAction* act : findChildren<QAction*>())
+		if (!act->objectName().isEmpty())
+			connect (act, SIGNAL (triggered()), this, SLOT (slot_action()));
+}
+
+// =============================================================================
+//
+KeySequenceConfig* MainWindow::shortcutForAction (QAction* action)
+{
+	QString keycfgname = format ("key_%1", action->objectName());
+	return KeySequenceConfig::getByName (keycfgname);
+}
+
+// =============================================================================
+//
+void MainWindow::updateActionShortcuts()
+{
+	for (QAction* act : findChildren<QAction*>())
+	{
+		KeySequenceConfig* cfg = shortcutForAction (act);
+
+		if (cfg)
+			act->setShortcut (cfg->getValue());
+	}
+}
+
+// =============================================================================
+//
+void MainWindow::slot_action()
+{
+	// Get the name of the sender object and use it to compose the slot name,
+	// then invoke this slot to call the action.
+	QMetaObject::invokeMethod (this,
+		qPrintable (format ("slot_%1", sender()->objectName())), Qt::DirectConnection);
+	endAction();
+}
+
+// =============================================================================
+//
+void MainWindow::endAction()
+{
+	// Add a step in the history now.
+	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.
+	updateDocumentListItem (getCurrentDocument());
+}
+
+// =============================================================================
+//
+void MainWindow::slot_lastSecondCleanup()
+{
+	delete m_renderer;
+	delete ui;
+}
+
+// =============================================================================
+//
+void MainWindow::updateRecentFilesMenu()
+{
+	// First, clear any items in the recent files menu
+for (QAction * recent : m_recentFiles)
+		delete recent;
+
+	m_recentFiles.clear();
+
+	QAction* first = null;
+
+	for (const QVariant& it : io_recentfiles)
+	{
+		QString file = it.toString();
+		QAction* recent = new QAction (getIcon ("open-recent"), file, this);
+
+		connect (recent, SIGNAL (triggered()), this, SLOT (slot_recentFile()));
+		ui->menuOpenRecent->insertAction (first, recent);
+		m_recentFiles << recent;
+		first = recent;
+	}
+}
+
+// =============================================================================
+//
+QList<LDQuickColor> quickColorsFromConfig()
+{
+	QList<LDQuickColor> colors;
+
+	for (QString colorname : gui_colortoolbar.split (":"))
+	{
+		if (colorname == "|")
+			colors << LDQuickColor::getSeparator();
+		else
+		{
+			LDColor* col = getColor (colorname.toLong());
+
+			if (col != null)
+				colors << LDQuickColor (col, null);
+		}
+	}
+
+	return colors;
+}
+
+// =============================================================================
+//
+void MainWindow::updateColorToolbar()
+{
+	m_colorButtons.clear();
+	ui->colorToolbar->clear();
+
+	for (LDQuickColor& entry : m_quickColors)
+	{
+		if (entry.isSeparator())
+			ui->colorToolbar->addSeparator();
+		else
+		{
+			QToolButton* colorButton = new QToolButton;
+			colorButton->setIcon (makeColorIcon (entry.color(), 22));
+			colorButton->setIconSize (QSize (22, 22));
+			colorButton->setToolTip (entry.color()->name);
+
+			connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor()));
+			ui->colorToolbar->addWidget (colorButton);
+			m_colorButtons << colorButton;
+
+			entry.setToolButton (colorButton);
+		}
+	}
+
+	updateGridToolBar();
+}
+
+// =============================================================================
+//
+void MainWindow::updateGridToolBar()
+{
+	// Ensure that the current grid - and only the current grid - is selected.
+	ui->actionGridCoarse->setChecked (grid == Grid::Coarse);
+	ui->actionGridMedium->setChecked (grid == Grid::Medium);
+	ui->actionGridFine->setChecked (grid == Grid::Fine);
+}
+
+// =============================================================================
+//
+void MainWindow::updateTitle()
+{
+	QString title = format (APPNAME " %1", fullVersionString());
+
+	// Append our current file if we have one
+	if (getCurrentDocument())
+	{
+		if (getCurrentDocument()->name().length() > 0)
+			title += format (": %1", basename (getCurrentDocument()->name()));
+		else
+			title += format (": <anonymous>");
+
+		if (getCurrentDocument()->getObjectCount() > 0 &&
+				getCurrentDocument()->getObject (0)->type() == LDObject::EComment)
+		{
+			// Append title
+			LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0));
+			title += format (": %1", comm->text());
+		}
+
+		if (getCurrentDocument()->hasUnsavedChanges())
+			title += '*';
+	}
+
+#ifdef DEBUG
+	title += " [debug build]";
+#elif BUILD_ID != BUILD_RELEASE
+	title += " [pre-release build]";
+#endif // DEBUG
+
+#ifdef COMPILE_DATE
+	title += " (built " COMPILE_DATE ")";
+#endif // COMPILE_DATE
+
+	setWindowTitle (title);
+}
+
+// =============================================================================
+//
+int MainWindow::deleteSelection()
+{
+	if (selection().isEmpty())
+		return 0;
+
+	LDObjectList selCopy = selection();
+
+	// Delete the objects that were being selected
+	for (LDObject* obj : selCopy)
+		obj->destroy();
+
+	refresh();
+	return selCopy.size();
+}
+
+// =============================================================================
+//
+void MainWindow::buildObjList()
+{
+	if (!getCurrentDocument())
+		return;
+
+	// Lock the selection while we do this so that refreshing the object list
+	// doesn't trigger selection updating so that the selection doesn't get lost
+	// while this is done.
+	g_isSelectionLocked = true;
+
+	for (int i = 0; i < ui->objectList->count(); ++i)
+		delete ui->objectList->item (i);
+
+	ui->objectList->clear();
+
+	for (LDObject* obj : getCurrentDocument()->objects())
+	{
+		QString descr;
+
+		switch (obj->type())
+		{
+			case LDObject::EComment:
+			{
+				descr = static_cast<LDComment*> (obj)->text();
+
+				// Remove leading whitespace
+				while (descr[0] == ' ')
+					descr.remove (0, 1);
+
+				break;
+			}
+
+			case LDObject::EEmpty:
+				break; // leave it empty
+
+			case LDObject::ELine:
+			case LDObject::ETriangle:
+			case LDObject::EQuad:
+			case LDObject::ECondLine:
+			{
+				for (int i = 0; i < obj->vertices(); ++i)
+				{
+					if (i != 0)
+						descr += ", ";
+
+					descr += obj->vertex (i).toString (true);
+				}
+				break;
+			}
+
+			case LDObject::EError:
+			{
+				descr = format ("ERROR: %1", obj->asText());
+				break;
+			}
+
+			case LDObject::EVertex:
+			{
+				descr = static_cast<LDVertex*> (obj)->pos.toString (true);
+				break;
+			}
+
+			case LDObject::ESubfile:
+			{
+				LDSubfile* ref = static_cast<LDSubfile*> (obj);
+
+				descr = format ("%1 %2, (", ref->fileInfo()->getDisplayName(), ref->position().toString (true));
+
+				for (int i = 0; i < 9; ++i)
+					descr += format ("%1%2", ref->transform()[i], (i != 8) ? " " : "");
+
+				descr += ')';
+				break;
+			}
+
+			case LDObject::EBFC:
+			{
+				descr = LDBFC::k_statementStrings[static_cast<LDBFC*> (obj)->statement()];
+				break;
+			}
+
+			case LDObject::EOverlay:
+			{
+				LDOverlay* ovl = static_cast<LDOverlay*> (obj);
+				descr = format ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->camera()],
+					basename (ovl->fileName()), ovl->x(), ovl->y(),
+					ovl->width(), ovl->height());
+				break;
+			}
+
+			default:
+			{
+				descr = obj->typeName();
+				break;
+			}
+		}
+
+		QListWidgetItem* item = new QListWidgetItem (descr);
+		item->setIcon (getIcon (obj->typeName()));
+
+		// Use italic font if hidden
+		if (obj->isHidden())
+		{
+			QFont font = item->font();
+			font.setItalic (true);
+			item->setFont (font);
+		}
+
+		// Color gibberish orange on red so it stands out.
+		if (obj->type() == LDObject::EError)
+		{
+			item->setBackground (QColor ("#AA0000"));
+			item->setForeground (QColor ("#FFAA00"));
+		}
+		elif (lv_colorize && obj->isColored() && obj->color() != maincolor && obj->color() != edgecolor)
+		{
+			// If the object isn't in the main or edge color, draw this
+			// list entry in said color.
+			LDColor* col = getColor (obj->color());
+
+			if (col)
+				item->setForeground (col->faceColor);
+		}
+
+		obj->qObjListEntry = item;
+		ui->objectList->insertItem (ui->objectList->count(), item);
+	}
+
+	g_isSelectionLocked = false;
+	updateSelection();
+	scrollToSelection();
+}
+
+// =============================================================================
+//
+void MainWindow::scrollToSelection()
+{
+	if (selection().isEmpty())
+		return;
+
+	LDObject* obj = selection().last();
+	ui->objectList->scrollToItem (obj->qObjListEntry);
+}
+
+// =============================================================================
+//
+void MainWindow::slot_selectionChanged()
+{
+	if (g_isSelectionLocked == true || getCurrentDocument() == null)
+		return;
+
+	// Update the shared selection array, though don't do this if this was
+	// called during GL picking, in which case the GL renderer takes care
+	// of the selection.
+	if (m_renderer->isPicking())
+		return;
+
+	LDObjectList priorSelection = selection();
+
+	// Get the objects from the object list selection
+	getCurrentDocument()->clearSelection();
+	const QList<QListWidgetItem*> items = ui->objectList->selectedItems();
+
+	for (LDObject* obj : getCurrentDocument()->objects())
+	{
+		for (QListWidgetItem* item : items)
+		{
+			if (item == obj->qObjListEntry)
+			{
+				obj->select();
+				break;
+			}
+		}
+	}
+
+	// Update the GL renderer
+	LDObjectList compound = priorSelection + selection();
+	removeDuplicates (compound);
+
+	for (LDObject* obj : compound)
+		m_renderer->compileObject (obj);
+
+	m_renderer->update();
+}
+
+// =============================================================================
+//
+void MainWindow::slot_recentFile()
+{
+	QAction* qAct = static_cast<QAction*> (sender());
+	openMainFile (qAct->text());
+}
+
+// =============================================================================
+//
+void MainWindow::slot_quickColor()
+{
+	QToolButton* button = static_cast<QToolButton*> (sender());
+	LDColor* col = null;
+
+	for (const LDQuickColor& entry : m_quickColors)
+	{
+		if (entry.toolButton() == button)
+		{
+			col = entry.color();
+			break;
+		}
+	}
+
+	if (col == null)
+		return;
+
+	int newColor = col->index;
+
+	for (LDObject* obj : selection())
+	{
+		if (obj->isColored() == false)
+			continue; // uncolored object
+
+		obj->setColor (newColor);
+		R()->compileObject (obj);
+	}
+
+	endAction();
+	refresh();
+}
+
+// =============================================================================
+//
+int MainWindow::getInsertionPoint()
+{
+	// If we have a selection, put the item after it.
+	if (!selection().isEmpty())
+		return selection().last()->lineNumber() + 1;
+
+	// Otherwise place the object at the end.
+	return getCurrentDocument()->getObjectCount();
+}
+
+// =============================================================================
+//
+void MainWindow::doFullRefresh()
+{
+	buildObjList();
+	m_renderer->hardRefresh();
+}
+
+// =============================================================================
+//
+void MainWindow::refresh()
+{
+	buildObjList();
+	m_renderer->update();
+}
+
+// =============================================================================
+//
+void MainWindow::updateSelection()
+{
+	g_isSelectionLocked = true;
+
+	for (LDObject* obj : getCurrentDocument()->objects())
+		obj->setSelected (false);
+
+	ui->objectList->clearSelection();
+
+	for (LDObject* obj : selection())
+	{
+		if (obj->qObjListEntry == null)
+			continue;
+
+		obj->qObjListEntry->setSelected (true);
+		obj->setSelected (true);
+	}
+
+	g_isSelectionLocked = false;
+	slot_selectionChanged();
+}
+
+// =============================================================================
+//
+int MainWindow::getSelectedColor()
+{
+	int result = -1;
+
+	for (LDObject* obj : selection())
+	{
+		if (obj->isColored() == false)
+			continue; // doesn't use color
+
+		if (result != -1 && obj->color() != result)
+			return -1; // No consensus in object color
+
+		if (result == -1)
+			result = obj->color();
+	}
+
+	return result;
+}
+
+// =============================================================================
+//
+LDObject::Type MainWindow::getUniformSelectedType()
+{
+	LDObject::Type result = LDObject::EUnidentified;
+
+	for (LDObject* obj : selection())
+	{
+		if (result != LDObject::EUnidentified && obj->color() != result)
+			return LDObject::EUnidentified;
+
+		if (result == LDObject::EUnidentified)
+			result = obj->type();
+	}
+
+	return result;
+}
+
+// =============================================================================
+//
+void MainWindow::closeEvent (QCloseEvent* ev)
+{
+	// Check whether it's safe to close all files.
+	if (!safeToCloseAll())
+	{
+		ev->ignore();
+		return;
+	}
+
+	// Save the configuration before leaving so that, for instance, grid choice
+	// is preserved across instances.
+	Config::save();
+
+	ev->accept();
+}
+
+// =============================================================================
+//
+void MainWindow::spawnContextMenu (const QPoint pos)
+{
+	const bool single = (selection().size() == 1);
+	LDObject* singleObj = (single) ? selection()[0] : null;
+
+	QMenu* contextMenu = new QMenu;
+
+	if (single && singleObj->type() != LDObject::EEmpty)
+	{
+		contextMenu->addAction (ui->actionEdit);
+		contextMenu->addSeparator();
+	}
+
+	contextMenu->addAction (ui->actionCut);
+	contextMenu->addAction (ui->actionCopy);
+	contextMenu->addAction (ui->actionPaste);
+	contextMenu->addAction (ui->actionDelete);
+	contextMenu->addSeparator();
+	contextMenu->addAction (ui->actionSetColor);
+
+	if (single)
+		contextMenu->addAction (ui->actionEditRaw);
+
+	contextMenu->addAction (ui->actionBorders);
+	contextMenu->addAction (ui->actionSetOverlay);
+	contextMenu->addAction (ui->actionClearOverlay);
+	contextMenu->addAction (ui->actionModeSelect);
+	contextMenu->addAction (ui->actionModeDraw);
+	contextMenu->addAction (ui->actionModeCircle);
+
+	if (selection().size() > 0)
+	{
+		contextMenu->addSeparator();
+		contextMenu->addAction (ui->actionSubfileSelection);
+	}
+
+	if (R()->camera() != GL::EFreeCamera)
+	{
+		contextMenu->addSeparator();
+		contextMenu->addAction (ui->actionSetDrawDepth);
+	}
+
+	contextMenu->exec (pos);
+}
+
+// =============================================================================
+//
+void MainWindow::deleteByColor (int colnum)
+{
+	LDObjectList objs;
+
+	for (LDObject* obj : getCurrentDocument()->objects())
+	{
+		if (!obj->isColored() || obj->color() != colnum)
+			continue;
+
+		objs << obj;
+	}
+
+	for (LDObject* obj : objs)
+		obj->destroy();
+}
+
+// =============================================================================
+//
+void MainWindow::updateEditModeActions()
+{
+	const EditMode mode = R()->editMode();
+	ui->actionModeSelect->setChecked (mode == ESelectMode);
+	ui->actionModeDraw->setChecked (mode == EDrawMode);
+	ui->actionModeCircle->setChecked (mode == ECircleMode);
+}
+
+// =============================================================================
+//
+void MainWindow::slot_editObject (QListWidgetItem* listitem)
+{
+	LDObject* obj = null;
+
+	for (LDObject* it : getCurrentDocument()->objects())
+	{
+		if (it->qObjListEntry == listitem)
+		{
+			obj = it;
+			break;
+		}
+	}
+
+	AddObjectDialog::staticDialog (obj->type(), obj);
+}
+
+// =============================================================================
+//
+bool MainWindow::save (LDDocument* doc, bool saveAs)
+{
+	QString path = doc->fullPath();
+
+	if (saveAs || path.isEmpty())
+	{
+		QString name = doc->defaultName();
+
+		if (!doc->fullPath().isEmpty()) 
+			name = doc->fullPath();
+		elif (!doc->name().isEmpty())
+			name = doc->name();
+
+		name.replace ("\\", "/");
+		path = QFileDialog::getSaveFileName (g_win, tr ("Save As"),
+			name, tr ("LDraw files (*.dat *.ldr)"));
+
+		if (path.isEmpty())
+		{
+			// User didn't give a file name, abort.
+			return false;
+		}
+	}
+
+	if (doc->save (path))
+	{
+		if (doc == getCurrentDocument())
+			updateTitle();
+
+		print ("Saved to %1.", path);
+
+		// Add it to recent files
+		addRecentFile (path);
+		return true;
+	}
+
+	QString message = format (tr ("Failed to save to %1: %2"), path, strerror (errno));
+
+	// Tell the user the save failed, and give the option for saving as with it.
+	QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win);
+
+	// Add a save-as button
+	QPushButton* saveAsBtn = new QPushButton (tr ("Save As"));
+	saveAsBtn->setIcon (getIcon ("file-save-as"));
+	dlg.addButton (saveAsBtn, QMessageBox::ActionRole);
+	dlg.setDefaultButton (QMessageBox::Close);
+	dlg.exec();
+
+	if (dlg.clickedButton() == saveAsBtn)
+		return save (doc, true); // yay recursion!
+
+	return false;
+}
+
+void MainWindow::addMessage (QString msg)
+{
+	m_msglog->addLine (msg);
+}
+
+// ============================================================================
+void ObjectList::contextMenuEvent (QContextMenuEvent* ev)
+{
+	g_win->spawnContextMenu (ev->globalPos());
+}
+
+// =============================================================================
+//
+QPixmap getIcon (QString iconName)
+{
+	return (QPixmap (format (":/icons/%1.png", iconName)));
+}
+
+// =============================================================================
+//
+bool confirm (const QString& message)
+{
+	return confirm (MainWindow::tr ("Confirm"), message);
+}
+
+// =============================================================================
+//
+bool confirm (const QString& title, const QString& message)
+{
+	return QMessageBox::question (g_win, title, message,
+		(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes;
+}
+
+// =============================================================================
+//
+void critical (const QString& message)
+{
+	QMessageBox::critical (g_win, MainWindow::tr ("Error"), message,
+		(QMessageBox::Close), QMessageBox::Close);
+}
+
+// =============================================================================
+//
+QIcon makeColorIcon (LDColor* colinfo, const int size)
+{
+	// Create an image object and link a painter to it.
+	QImage img (size, size, QImage::Format_ARGB32);
+	QPainter paint (&img);
+	QColor col = colinfo->faceColor;
+
+	if (colinfo->index == maincolor)
+	{
+		// Use the user preferences for main color here
+		col = gl_maincolor;
+		col.setAlphaF (gl_maincolor_alpha);
+	}
+
+	// Paint the icon border
+	paint.fillRect (QRect (0, 0, size, size), colinfo->edgeColor);
+
+	// Paint the checkerboard background, visible with translucent icons
+	paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8));
+
+	// Paint the color above the checkerboard
+	paint.fillRect (QRect (1, 1, size - 2, size - 2), col);
+	return QIcon (QPixmap::fromImage (img));
+}
+
+// =============================================================================
+//
+void makeColorComboBox (QComboBox* box)
+{
+	std::map<int, int> counts;
+
+	for (LDObject* obj : getCurrentDocument()->objects())
+	{
+		if (!obj->isColored())
+			continue;
+
+		if (counts.find (obj->color()) == counts.end())
+			counts[obj->color()] = 1;
+		else
+			counts[obj->color()]++;
+	}
+
+	box->clear();
+	int row = 0;
+
+	for (const auto& pair : counts)
+	{
+		LDColor* col = getColor (pair.first);
+		assert (col != null);
+
+		QIcon ico = makeColorIcon (col, 16);
+		box->addItem (ico, format ("[%1] %2 (%3 object%4)",
+			pair.first, col->name, pair.second, plural (pair.second)));
+		box->setItemData (row, pair.first);
+
+		++row;
+	}
+}
+
+// =============================================================================
+//
+void MainWindow::updateDocumentList()
+{
+	m_updatingTabs = true;
+
+	while (m_tabs->count() > 0)
+		m_tabs->removeTab (0);
+
+	for (LDDocument* f : g_loadedFiles)
+	{
+		// Don't list implicit files unless explicitly desired.
+		if (f->isImplicit() && !gui_implicitfiles)
+			continue;
+
+		// Add an item to the list for this file and store the tab index
+		// in the document so we can find documents by tab index.
+		f->setTabIndex (m_tabs->addTab (""));
+		updateDocumentListItem (f);
+	}
+
+	m_updatingTabs = false;
+}
+
+// =============================================================================
+//
+void MainWindow::updateDocumentListItem (LDDocument* doc)
+{
+	bool oldUpdatingTabs = m_updatingTabs;
+	m_updatingTabs = true;
+
+	if (doc->tabIndex() == -1)
+	{
+		// 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.
+		updateDocumentList();
+		return;
+	}
+
+	// If this is the current file, it also needs to be the selected item on
+	// the list.
+	if (doc == getCurrentDocument())
+		m_tabs->setCurrentIndex (doc->tabIndex());
+
+	m_tabs->setTabText (doc->tabIndex(), doc->getDisplayName());
+
+	// If the document.has unsaved changes, draw a little icon next to it to mark that.
+	m_tabs->setTabIcon (doc->tabIndex(), doc->hasUnsavedChanges() ? getIcon ("file-save") : QIcon());
+	m_updatingTabs = oldUpdatingTabs;
+}
+
+// =============================================================================
+//
+// 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 MainWindow::changeCurrentFile()
+{
+	if (m_updatingTabs)
+		return;
+
+	LDDocument* f = null;
+	int tabIndex = m_tabs->currentIndex();
+
+	// Find the file pointer of the item that was selected.
+	for (LDDocument* it : g_loadedFiles)
+	{
+		if (it->tabIndex() == tabIndex)
+		{
+			f = it;
+			break;
+		}
+	}
+
+	// If we picked the same file we're currently on, we don't need to do
+	// anything.
+	if (f == null || f == getCurrentDocument())
+		return;
+
+	LDDocument::setCurrent (f);
+}
+
+// =============================================================================
+//
+void MainWindow::refreshObjectList()
+{
+#if 0
+	ui->objectList->clear();
+	LDDocument* f = getCurrentDocument();
+
+for (LDObject* obj : *f)
+		ui->objectList->addItem (obj->qObjListEntry);
+
+#endif
+
+	buildObjList();
+}
+
+// =============================================================================
+//
+void MainWindow::updateActions()
+{
+	History* his = getCurrentDocument()->history();
+	int pos = his->position();
+	ui->actionUndo->setEnabled (pos != -1);
+	ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1);
+	ui->actionAxes->setChecked (gl_axes);
+	ui->actionBFCView->setChecked (gl_colorbfc);
+	ui->actionDrawAngles->setChecked (gl_drawangles);
+}
+
+// =============================================================================
+//
+QImage imageFromScreencap (uchar* data, int w, int h)
+{
+	// GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well.
+	return QImage (data, w, h, QImage::Format_ARGB32).rgbSwapped().mirrored();
+}
+
+// =============================================================================
+//
+LDQuickColor::LDQuickColor (LDColor* color, QToolButton* toolButton) :
+	m_color (color),
+	m_toolButton (toolButton) {}
+
+// =============================================================================
+//
+LDQuickColor LDQuickColor::getSeparator()
+{
+	return LDQuickColor (null, null);
+}
+
+// =============================================================================
+//
+bool LDQuickColor::isSeparator() const
+{
+	return color() == null;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mainWindow.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,378 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QMainWindow>
+#include <QAction>
+#include <QListWidget>
+#include <QRadioButton>
+#include "configuration.h"
+#include "ldObject.h"
+#include "ui_ldforge.h"
+
+class MessageManager;
+class MainWindow;
+class LDColor;
+class QToolButton;
+class QDialogButtonBox;
+class GLRenderer;
+class QComboBox;
+class QProgressBar;
+class Ui_LDForgeUI;
+
+// Stuff for dialogs
+#define IMPLEMENT_DIALOG_BUTTONS \
+	bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); \
+	connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept())); \
+	connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); \
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+#define DEFINE_ACTION(NAME, DEFSHORTCUT) \
+	cfg (KeySequence, key_action##NAME, DEFSHORTCUT); \
+	void MainWindow::slot_action##NAME()
+
+// Convenience macros for key sequences.
+#define KEY(N) (Qt::Key_##N)
+#define CTRL(N) (Qt::CTRL | Qt::Key_##N)
+#define SHIFT(N) (Qt::SHIFT | Qt::Key_##N)
+#define CTRL_SHIFT(N) (Qt::CTRL | Qt::SHIFT | Qt::Key_##N)
+
+// =============================================================================
+class LDQuickColor
+{
+	PROPERTY (public,	LDColor*,		color,		setColor,		STOCK_WRITE)
+	PROPERTY (public,	QToolButton*,	toolButton,	setToolButton,	STOCK_WRITE)
+
+	public:
+		LDQuickColor (LDColor* color, QToolButton* toolButton);
+		bool isSeparator() const;
+
+		static LDQuickColor getSeparator();
+};
+
+//!
+//! Object list class for MainWindow
+//!
+class ObjectList : public QListWidget
+{
+	Q_OBJECT
+
+	protected:
+		void contextMenuEvent (QContextMenuEvent* ev);
+};
+
+//!
+//! \brief The main window class.
+//!
+//! The MainWindow is LDForge's main GUI. It hosts the renderer, the object list,
+//! the message log, etc. Contains \c slot_action(), which is what all actions
+//! connect to.
+//!
+class MainWindow : public QMainWindow
+{
+	Q_OBJECT
+
+	public:
+		//! Constructs the main window
+		explicit MainWindow (QWidget* parent = null, Qt::WindowFlags flags = 0);
+
+		//! Rebuilds the object list, located to the right of the GUI.
+		void buildObjList();
+
+		//! Updates the window title.
+		void updateTitle();
+
+		//! Builds the object list and tells the GL renderer to init a full
+		//! refresh.
+		void doFullRefresh();
+
+		//! Builds the object list and tells the GL renderer to do a soft update.
+		void refresh();
+
+		//! \returns the suggested position to place a new object at.
+		int getInsertionPoint();
+
+		//! Updates the quick color toolbar
+		void updateColorToolbar();
+
+		//! Rebuilds the recent files submenu
+		void updateRecentFilesMenu();
+
+		//! Sets the selection based on what's selected in the object list.
+		void updateSelection();
+
+		//! Updates the grids, selects the selected grid and deselects others.
+		void updateGridToolBar();
+
+		//! Updates the edit modes, current one is selected and others are deselected.
+		void updateEditModeActions();
+
+		//! Rebuilds the document tab list.
+		void updateDocumentList();
+
+		//! Updates the document tab for \c doc. If no such tab exists, the
+		//! document list is rebuilt instead.
+		void updateDocumentListItem (LDDocument* doc);
+
+		//! \returns the uniform selected color (i.e. 4 if everything selected is
+		//! red), -1 if there is no such consensus.
+		int getSelectedColor();
+
+		//! \returns the uniform selected type (i.e. \c LDObject::ELine if everything
+		//! selected is a line), \c LDObject::EUnidentified if there is no such
+		//! consensus.
+		LDObject::Type getUniformSelectedType();
+
+		//! Automatically scrolls the object list so that it points to the first
+		//! selected object.
+		void scrollToSelection();
+
+		//! Spawns the context menu at the given position.
+		void spawnContextMenu (const QPoint pos);
+
+		//! Deletes all selected objects.
+		//! \returns the count of deleted objects.
+		int deleteSelection();
+
+		//! Deletes all objects by the given color number.
+		void deleteByColor (int colnum);
+
+		//! Tries to save the given document.
+		//! \param doc the document to save
+		//! \param saveAs if true, always ask for a file path
+		//! \returns whether the save was successful
+		bool save (LDDocument* doc, bool saveAs);
+
+		//! Updates various actions, undo/redo are set enabled/disabled where
+		//! appropriate, togglable actions are updated based on configuration,
+		//! etc.
+		void updateActions();
+
+		//! \returns a pointer to the renderer
+		inline GLRenderer* R()
+		{
+			return m_renderer;
+		}
+
+		//! Sets the quick color list to the given list of colors.
+		inline void setQuickColors (const QList<LDQuickColor>& colors)
+		{
+			m_quickColors = colors;
+			updateColorToolbar();
+		}
+
+		//! Adds a message to the renderer's message manager.
+		void addMessage (QString msg);
+
+		//! Updates the object list. Right now this just rebuilds it.
+		void refreshObjectList();
+
+		//! Updates all actions to ensure they have the correct shortcut as
+		//! defined in the configuration entries.
+		void updateActionShortcuts();
+
+		//! Gets the shortcut configuration for the given \c action
+		KeySequenceConfig* shortcutForAction (QAction* action);
+
+		void endAction();
+
+	public slots:
+		void changeCurrentFile();
+		void slot_action();
+		void slot_actionNew();
+		void slot_actionNewFile();
+		void slot_actionOpen();
+		void slot_actionDownloadFrom();
+		void slot_actionSave();
+		void slot_actionSaveAs();
+		void slot_actionSaveAll();
+		void slot_actionClose();
+		void slot_actionCloseAll();
+		void slot_actionInsertFrom();
+		void slot_actionExportTo();
+		void slot_actionSettings();
+		void slot_actionSetLDrawPath();
+		void slot_actionScanPrimitives();
+		void slot_actionExit();
+		void slot_actionResetView();
+		void slot_actionAxes();
+		void slot_actionWireframe();
+		void slot_actionBFCView();
+		void slot_actionSetOverlay();
+		void slot_actionClearOverlay();
+		void slot_actionScreenshot();
+		void slot_actionInsertRaw();
+		void slot_actionNewSubfile();
+		void slot_actionNewLine();
+		void slot_actionNewTriangle();
+		void slot_actionNewQuad();
+		void slot_actionNewCLine();
+		void slot_actionNewComment();
+		void slot_actionNewBFC();
+		void slot_actionNewVertex();
+		void slot_actionUndo();
+		void slot_actionRedo();
+		void slot_actionCut();
+		void slot_actionCopy();
+		void slot_actionPaste();
+		void slot_actionDelete();
+		void slot_actionSelectAll();
+		void slot_actionSelectByColor();
+		void slot_actionSelectByType();
+		void slot_actionModeDraw();
+		void slot_actionModeSelect();
+		void slot_actionModeCircle();
+		void slot_actionSetDrawDepth();
+		void slot_actionSetColor();
+		void slot_actionAutocolor();
+		void slot_actionUncolorize();
+		void slot_actionInline();
+		void slot_actionInlineDeep();
+		void slot_actionInvert();
+		void slot_actionMakePrimitive();
+		void slot_actionSplitQuads();
+		void slot_actionEditRaw();
+		void slot_actionBorders();
+		void slot_actionCornerVerts();
+		void slot_actionRoundCoordinates();
+		void slot_actionVisibilityHide();
+		void slot_actionVisibilityReveal();
+		void slot_actionVisibilityToggle();
+		void slot_actionReplaceCoords();
+		void slot_actionFlip();
+		void slot_actionDemote();
+		void slot_actionYtruder();
+		void slot_actionRectifier();
+		void slot_actionIntersector();
+		void slot_actionIsecalc();
+		void slot_actionCoverer();
+		void slot_actionEdger2();
+		void slot_actionHelp();
+		void slot_actionAbout();
+		void slot_actionAboutQt();
+		void slot_actionGridCoarse();
+		void slot_actionGridMedium();
+		void slot_actionGridFine();
+		void slot_actionEdit();
+		void slot_actionMoveUp();
+		void slot_actionMoveDown();
+		void slot_actionMoveXNeg();
+		void slot_actionMoveXPos();
+		void slot_actionMoveYNeg();
+		void slot_actionMoveYPos();
+		void slot_actionMoveZNeg();
+		void slot_actionMoveZPos();
+		void slot_actionRotateXNeg();
+		void slot_actionRotateXPos();
+		void slot_actionRotateYNeg();
+		void slot_actionRotateYPos();
+		void slot_actionRotateZNeg();
+		void slot_actionRotateZPos();
+		void slot_actionRotationPoint();
+		void slot_actionAddHistoryLine();
+		void slot_actionJumpTo();
+		void slot_actionSubfileSelection();
+		void slot_actionDrawAngles();
+
+	protected:
+		void closeEvent (QCloseEvent* ev);
+
+	private:
+		GLRenderer*			m_renderer;
+		LDObjectList		m_sel;
+		QList<LDQuickColor>	m_quickColors;
+		QList<QToolButton*>	m_colorButtons;
+		QList<QAction*>		m_recentFiles;
+		MessageManager*		m_msglog;
+		Ui_LDForgeUI*		ui;
+		QTabBar*			m_tabs;
+		bool				m_updatingTabs;
+
+	private slots:
+		void slot_selectionChanged();
+		void slot_recentFile();
+		void slot_quickColor();
+		void slot_lastSecondCleanup();
+		void slot_editObject (QListWidgetItem* listitem);
+};
+
+//! Pointer to the instance of MainWindow.
+extern MainWindow* g_win;
+
+//! Get an icon by name from the resources directory.
+QPixmap getIcon (QString iconName);
+
+//! \returns a list of quick colors based on the configuration entry.
+QList<LDQuickColor> quickColorsFromConfig();
+
+//! Asks the user a yes/no question with the given \c message and the given
+//! window \c title.
+//! \returns true if the user answered yes, false if no.
+bool confirm (const QString& title, const QString& message); // Generic confirm prompt
+
+//! An overload of \c confirm(), this asks the user a yes/no question with the
+//! given \c message.
+//! \returns true if the user answered yes, false if no.
+bool confirm (const QString& message);
+
+//! Displays an error prompt with the given \c message
+void critical (const QString& message);
+
+//! Makes an icon of \c size x \c size pixels to represent \c colinfo
+QIcon makeColorIcon (LDColor* colinfo, const int size);
+
+//! Fills the given combo-box with color information
+void makeColorComboBox (QComboBox* box);
+
+//! \returns a QImage from the given raw GL \c data
+QImage imageFromScreencap (uchar* data, int w, int h);
+
+//!
+//! Takes in pairs of radio buttons and respective values and finds the first
+//! selected one.
+//! \returns returns the value of the first found radio button that was checked
+//! \returns by the user.
+//!
+template<class T>
+T radioSwitch (const T& defval, QList<Pair<QRadioButton*, T>> haystack)
+{
+	for (Pair<QRadioButton*, const T&> i : haystack)
+		if (i.first->isChecked())
+			return i.second;
+
+	return defval;
+}
+
+//!
+//! Takes in pairs of radio buttons and respective values and checks the first
+//! found radio button whose respsective value matches \c expr have the given value.
+//!
+template<class T>
+void radioDefault (const T& expr, QList<Pair<QRadioButton*, T>> haystack)
+{
+	for (Pair<QRadioButton*, const T&> i : haystack)
+	{
+		if (i.second == expr)
+		{
+			i.first->setChecked (true);
+			return;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/messageLog.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,131 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QTimer>
+#include <QDate>
+#include "messageLog.h"
+#include "glRenderer.h"
+#include "mainWindow.h"
+
+static const int g_maxMessages = 5;
+static const int g_expiry = 5;
+static const int g_fadeTime = 500; // msecs
+
+// =============================================================================
+//
+MessageManager::MessageManager (QObject* parent) :
+			QObject (parent)
+{
+	m_ticker = new QTimer;
+	m_ticker->start (100);
+	connect (m_ticker, SIGNAL (timeout()), this, SLOT (tick()));
+}
+
+// =============================================================================
+//
+MessageManager::Line::Line (QString text) :
+			text (text),
+			alpha (1.0f),
+			expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {}
+
+// =============================================================================
+//
+bool MessageManager::Line::update (bool& changed)
+{
+	changed = false;
+	QDateTime now = QDateTime::currentDateTime();
+	int msec = now.msecsTo (expiry);
+
+	if (now >= expiry)
+	{
+		// Message line has expired
+		changed = true;
+		return false;
+	}
+
+	if (msec <= g_fadeTime)
+	{
+		// Message line has not expired but is fading out
+		alpha = ( (float) msec) / g_fadeTime;
+		changed = true;
+	}
+
+	return true;
+}
+
+// =============================================================================
+// Add a line to the message manager.
+//
+void MessageManager::addLine (QString line)
+{
+	// If there's too many entries, pop the excess out
+	while (m_lines.size() >= g_maxMessages)
+		m_lines.removeFirst();
+
+	m_lines << Line (line);
+
+	// Update the renderer view
+	if (renderer())
+		renderer()->update();
+}
+
+// =============================================================================
+// Ticks the message manager. All lines are ticked and the renderer scene is
+// redrawn if something changed.
+//
+void MessageManager::tick()
+{
+	if (m_lines.isEmpty())
+		return;
+
+	bool changed = false;
+
+	for (int i = 0; i < m_lines.size(); ++i)
+	{
+		bool lineChanged;
+
+		if (!m_lines[i].update (lineChanged))
+			m_lines.removeAt (i--);
+
+		changed |= lineChanged;
+	}
+
+	if (changed && renderer())
+		renderer()->update();
+}
+
+// =============================================================================
+//
+const QList<MessageManager::Line>& MessageManager::getLines() const
+{
+	return m_lines;
+}
+
+// =============================================================================
+//
+void printToLog (const QString& msg)
+{
+	for (QString& a : msg.split ("\n", QString::SkipEmptyParts))
+	{
+		if (g_win != null)
+			g_win->addMessage (a);
+
+		// Also print it to stdout
+		fprint (stdout, "%1\n", a);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/messageLog.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,84 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QObject>
+#include <QDate>
+#include "main.h"
+#include "basics.h"
+
+class GLRenderer;
+class QTimer;
+
+//!
+//! \brief Manages the list of messages at the top-left of the renderer.
+//!
+//! The message manager is an object which keeps track of messages that appear
+//! on the renderer's screen. Each line is contained in a separate object which
+//! contains the text, expiry time and alpha. The message manager is doubly
+//! linked to its corresponding renderer.
+//!
+//! Message manager calls its \c tick() function regularly to update the messages,
+//! where each line's expiry is checked for. Lines begin to fade out when nearing
+//! their expiry. If the message manager's lines change, the renderer undergoes
+//! repainting.
+//!
+class MessageManager : public QObject
+{
+	Q_OBJECT
+	PROPERTY (public, GLRenderer*, renderer, setRenderer, STOCK_WRITE)
+
+	public:
+		//! \class MessageManager::Line
+		//! A single line of the message log.
+		class Line
+		{
+			public:
+				//! Constructs a line with the given \c text
+				Line (QString text);
+
+				//! Check this line's expiry and update alpha accordingly.
+				//! \c changed is updated to whether the line has somehow
+				//! changed since the last update.
+				//! \returns true if the line is to still stick around, false
+				//! \returns if it expired.
+				bool update (bool& changed);
+
+				QString text;
+				float alpha;
+				QDateTime expiry;
+		};
+
+		//! Constructs the message manager.
+		explicit MessageManager (QObject* parent = null);
+
+		//! Adds a line with the given \c text to the message manager.
+		void addLine (QString line);
+
+		//! \returns all active lines in the message manager.
+		const QList<Line>& getLines() const;
+
+	private:
+		QList<Line>	m_lines;
+		QTimer*		m_ticker;
+
+	private slots:
+		//! Ticks the manager. This is called by the timer to update
+		//! the messages.
+		void tick();
+};
--- a/src/misc/DocumentPointer.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "DocumentPointer.h"
-#include "../Document.h"
-#include "../Misc.h"
-
-LDDocumentPointer::LDDocumentPointer()  : m_pointer (null) {}
-
-// =============================================================================
-//
-LDDocumentPointer::LDDocumentPointer (LDDocument* ptr) :
-	m_pointer (ptr)
-{
-	addReference ();
-}
-
-// =============================================================================
-//
-LDDocumentPointer::LDDocumentPointer (const LDDocumentPointer& other) :
-	m_pointer (other.pointer())
-{
-	addReference ();
-}
-
-// =============================================================================
-//
-LDDocumentPointer::~LDDocumentPointer()
-{
-	removeReference();
-}
-
-// =============================================================================
-//
-void LDDocumentPointer::addReference()
-{
-	if (pointer() != null)
-		pointer()->addReference (this);
-}
-
-// =============================================================================
-//
-void LDDocumentPointer::removeReference()
-{
-	if (pointer() != null)
-		pointer()->removeReference (this);
-}
-
-// =============================================================================
-//
-LDDocumentPointer& LDDocumentPointer::operator= (LDDocument* ptr)
-{
-	if (ptr != pointer())
-	{
-		removeReference();
-		setPointer (ptr);
-		addReference();
-	}
-
-	return *this;
-}
\ No newline at end of file
--- a/src/misc/DocumentPointer.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include "../Main.h"
-
-class LDSubfile;
-class LDDocument;
-
-//!
-//! \brief A reference-counting pointer to LDDocument.
-//!
-//! The LDDocumentPointer class defines a reference-counting pointer which
-//! points to LDDocument.
-//!
-class LDDocumentPointer
-{
-	PROPERTY (private, LDDocument*, pointer, setPointer, STOCK_WRITE)
-
-	public:
-		//! Constructs a null LDDocumentPointer
-		LDDocumentPointer();
-
-		//! Constructs a document pointer with the given pointer
-		LDDocumentPointer (LDDocument* ptr);
-
-		//! Copy-constructs a LDDocumentPointer.
-		LDDocumentPointer (const LDDocumentPointer& other);
-
-		//! Destructs the pointer.
-		~LDDocumentPointer();
-
-		//! \param ptr the new pointer to change to.
-		LDDocumentPointer& operator= (LDDocument* ptr);
-
-		//! Copy operator.
-		//! \param other the pointer whose internal pointer to copy.
-		inline LDDocumentPointer& operator= (LDDocumentPointer& other)
-		{
-			return operator= (other.pointer());
-		}
-
-		//! Operator overload for a->b support.
-		inline LDDocument* operator->() const
-		{
-			return pointer();
-		}
-
-		//! Cast operator overload
-		inline operator LDDocument*() const
-		{
-			return pointer();
-		}
-
-	private:
-		void addReference();
-		void removeReference();
-};
--- a/src/misc/InvokationDeferer.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "InvokationDeferer.h"
-#include "../Misc.h"
-
-static InvokationDeferer* g_invokationDeferer = new InvokationDeferer();
-
-// =============================================================================
-//
-InvokationDeferer::InvokationDeferer (QObject* parent) : QObject (parent)
-{
-	connect (this, SIGNAL (functionAdded()), this, SLOT (invokeFunctions()),
-		Qt::QueuedConnection);
-}
-
-// =============================================================================
-//
-void InvokationDeferer::addFunctionCall (InvokationDeferer::FunctionType func)
-{
-	m_funcs << func;
-	removeDuplicates (m_funcs);
-	emit functionAdded();
-}
-
-// =============================================================================
-//
-void InvokationDeferer::invokeFunctions()
-{
-	for (FunctionType func : m_funcs)
-		(*func)();
-
-	m_funcs.clear();
-}
-
-// =============================================================================
-//
-void invokeLater (InvokationDeferer::FunctionType func)
-{
-	g_invokationDeferer->addFunctionCall (func);
-}
\ No newline at end of file
--- a/src/misc/InvokationDeferer.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include <QObject>
-
-class InvokationDeferer : public QObject
-{
-	Q_OBJECT
-
-	public:
-		using FunctionType = void(*)();
-
-		explicit InvokationDeferer (QObject* parent = 0);
-		void addFunctionCall (FunctionType func);
-
-	signals:
-		void functionAdded();
-
-	private:
-		QList<FunctionType>	m_funcs;
-
-	private slots:
-		void invokeFunctions();
-};
-
-void invokeLater (InvokationDeferer::FunctionType func);
--- a/src/misc/RingFinder.cc	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "RingFinder.h"
-#include "../Misc.h"
-
-RingFinder g_RingFinder;
-
-// =============================================================================
-//
-bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution)
-{
-	// Don't recurse too deep.
-	if (m_stack >= 5)
-		return false;
-
-	// Find the scale and number of a ring between r1 and r0.
-	assert (r1 >= r0);
-	double scale = r1 - r0;
-	double num = r0 / scale;
-
-	// If the ring number is integral, we have found a fitting ring to r0 -> r1!
-	if (isInteger (num))
-	{
-		Component cmp;
-		cmp.scale = scale;
-		cmp.num = (int) round (num);
-		currentSolution.addComponent (cmp);
-
-		// If we're still at the first recursion, this is the only
-		// ring and there's nothing left to do. Guess we found the winner.
-		if (m_stack == 0)
-		{
-			m_solutions.push_back (currentSolution);
-			return true;
-		}
-	}
-	else
-	{
-		// Try find solutions by splitting the ring in various positions.
-		if (isZero (r1 - r0))
-			return false;
-
-		double interval;
-
-		// Determine interval. The smaller delta between radii, the more precise
-		// interval should be used. We can't really use a 0.5 increment when
-		// calculating rings to 10 -> 105... that would take ages to process!
-		if (r1 - r0 < 0.5)
-			interval = 0.1;
-		else if (r1 - r0 < 10)
-			interval = 0.5;
-		else if (r1 - r0 < 50)
-			interval = 1;
-		else
-			interval = 5;
-
-		// Now go through possible splits and try find rings for both segments.
-		for (double r = r0 + interval; r < r1; r += interval)
-		{
-			Solution sol = currentSolution;
-
-			m_stack++;
-			bool res = findRingsRecursor (r0, r, sol) && findRingsRecursor (r, r1, sol);
-			m_stack--;
-
-			if (res)
-			{
-				// We succeeded in finding radii for this segment. If the stack is 0, this
-				// is the first recursion to this function. Thus there are no more ring segments
-				// to process and we can add the solution.
-				//
-				// If not, when this function ends, it will be called again with more arguments.
-				// Accept the solution to this segment by setting currentSolution to sol, and
-				// return true to continue processing.
-				if (m_stack == 0)
-					m_solutions.push_back (sol);
-				else
-				{
-					currentSolution = sol;
-					return true;
-				}
-			}
-		}
-
-		return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-// Main function. Call this with r0 and r1. If this returns true, use bestSolution
-// for the solution that was presented.
-//
-bool RingFinder::findRings (double r0, double r1)
-{
-	m_solutions.clear();
-	Solution sol;
-
-	// Recurse in and try find solutions.
-	findRingsRecursor (r0, r1, sol);
-
-	// Compare the solutions and find the best one. The solution class has an operator>
-	// overload to compare two solutions.
-	m_bestSolution = null;
-
-	for (QVector<Solution>::iterator solp = m_solutions.begin(); solp != m_solutions.end(); ++solp)
-	{
-		const Solution& sol = *solp;
-
-		if (m_bestSolution == null || sol.isSuperiorTo (m_bestSolution))
-			m_bestSolution = &sol;
-	}
-
-	return (m_bestSolution != null);
-}
-
-// =============================================================================
-//
-bool RingFinder::Solution::isSuperiorTo (const Solution* other) const
-{
-	// If this solution has less components than the other one, this one
-	// is definitely better.
-	if (getComponents().size() < other->getComponents().size())
-		return true;
-
-	// vice versa
-	if (other->getComponents().size() < getComponents().size())
-		return false;
-
-	// Calculate the maximum ring number. Since the solutions have equal
-	// ring counts, the solutions with lesser maximum rings should result
-	// in cleaner code and less new primitives, right?
-	int maxA = 0,
-		maxB = 0;
-
-	for (int i = 0; i < getComponents().size(); ++i)
-	{
-		maxA = max (getComponents()[i].num, maxA);
-		maxB = max (other->getComponents()[i].num, maxB);
-	}
-
-	if (maxA < maxB)
-		return true;
-
-	if (maxB < maxA)
-		return false;
-
-	// Solutions have equal rings and equal maximum ring numbers. Let's
-	// just say this one is better, at this point it does not matter which
-	// one is chosen.
-	return true;
-}
\ No newline at end of file
--- a/src/misc/RingFinder.h	Wed Mar 12 16:20:40 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include "../Main.h"
-
-//!
-//! \brief Provides an algorithm for finding solutions of rings between given radii.
-//!
-//! The RingFinder is a class which implements a ring finding algorithm. It is passed
-//! two radii and it tries to find solutions of rings that would fill the given space.
-//!
-//! \note It is not fool-proof and does not always yield a solution, never assume
-//! \note that one is a available as none is guaranteed.
-//!
-class RingFinder
-{
-	public:
-		//! A single component in a solution
-		struct Component
-		{
-			int num;
-			double scale;
-		};
-
-		//! A solution whose components would fill the desired ring space.
-		class Solution
-		{
-			public:
-				//! \returns components of this solution
-				inline const QVector<Component>& getComponents() const
-				{
-					return m_components;
-				}
-
-				//! Add a component to this solution
-				inline void addComponent (const Component& a)
-				{
-					m_components.push_back (a);
-				}
-
-				//! \brief Compare solutions.
-				//!
-				//! Compares this solution with \c other and determines which
-				//! one is superior.
-				//!
-				//! A solution is considered superior if solution has less
-				//! components than the other one. If both solution have an
-				//! equal amount components, the solution with a lesser maximum
-				//! ring number is found superior, as such solutions should
-				//! yield less new primitives and cleaner definitions.
-				//!
-				//! The solution which is found superior to every other solution
-				//! will be the one returned by \c RingFinder::bestSolution().
-				//!
-				//! \param other the solution to check against
-				//! \returns whether this solution is considered superior
-				//! \returns to \c other.
-				//!
-				bool isSuperiorTo (const Solution* other) const;
-
-			private:
-				QVector<Component> m_components;
-		};
-
-		//! Constructs a ring finder.
-		RingFinder() {}
-
-		//! \brief Tries to find rings between \c r0 and \c r1.
-		//!
-		//! This is the main algorithm of the ring finder. It tries to use math
-		//! to find the one ring between r0 and r1. If it fails (the ring number
-		//! is non-integral), it finds an intermediate radius (ceil of the ring
-		//! number times scale) and splits the radius at this point, calling this
-		//! function again to try find the rings between r0 - r and r - r1.
-		//!
-		//! This does not always yield into usable results. If at some point r ==
-		//! r0 or r == r1, there is no hope of finding the rings, at least with
-		//! this algorithm, as it would fall into an infinite recursion.
-		//!
-		//! \param r0 the lower radius of the ring space
-		//! \param r1 the higher radius of the ring space
-		//! \returns whether it was possible to find a solution for the given
-		//! \returns ring space.
-		//!
-		bool findRings (double r0, double r1);
-
-		//! \returns the solution that was considered best. Returns \c null
-		//! \returns if no suitable solution was found.
-		//! \see \c RingFinder::Solution::isSuperiorTo()
-		inline const Solution* bestSolution()
-		{
-			return m_bestSolution;
-		}
-
-		//! \returns all found solutions. The list is empty if no solutions
-		//! \returns were found.
-		inline const QVector<Solution>& allSolutions() const
-		{
-			return m_solutions;
-		}
-
-	private:
-		QVector<Solution> m_solutions;
-		const Solution*   m_bestSolution;
-		int               m_stack;
-
-		//! Helper function for \c findRings
-		bool findRingsRecursor (double r0, double r1, Solution& currentSolution);
-};
-
-extern RingFinder g_RingFinder;
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc/documentPointer.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,76 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "documentPointer.h"
+#include "../ldDocument.h"
+#include "../miscallenous.h"
+
+LDDocumentPointer::LDDocumentPointer()  : m_pointer (null) {}
+
+// =============================================================================
+//
+LDDocumentPointer::LDDocumentPointer (LDDocument* ptr) :
+	m_pointer (ptr)
+{
+	addReference ();
+}
+
+// =============================================================================
+//
+LDDocumentPointer::LDDocumentPointer (const LDDocumentPointer& other) :
+	m_pointer (other.pointer())
+{
+	addReference ();
+}
+
+// =============================================================================
+//
+LDDocumentPointer::~LDDocumentPointer()
+{
+	removeReference();
+}
+
+// =============================================================================
+//
+void LDDocumentPointer::addReference()
+{
+	if (pointer() != null)
+		pointer()->addReference (this);
+}
+
+// =============================================================================
+//
+void LDDocumentPointer::removeReference()
+{
+	if (pointer() != null)
+		pointer()->removeReference (this);
+}
+
+// =============================================================================
+//
+LDDocumentPointer& LDDocumentPointer::operator= (LDDocument* ptr)
+{
+	if (ptr != pointer())
+	{
+		removeReference();
+		setPointer (ptr);
+		addReference();
+	}
+
+	return *this;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc/documentPointer.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,74 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "../main.h"
+
+class LDSubfile;
+class LDDocument;
+
+//!
+//! \brief A reference-counting pointer to LDDocument.
+//!
+//! The LDDocumentPointer class defines a reference-counting pointer which
+//! points to LDDocument.
+//!
+class LDDocumentPointer
+{
+	PROPERTY (private, LDDocument*, pointer, setPointer, STOCK_WRITE)
+
+	public:
+		//! Constructs a null LDDocumentPointer
+		LDDocumentPointer();
+
+		//! Constructs a document pointer with the given pointer
+		LDDocumentPointer (LDDocument* ptr);
+
+		//! Copy-constructs a LDDocumentPointer.
+		LDDocumentPointer (const LDDocumentPointer& other);
+
+		//! Destructs the pointer.
+		~LDDocumentPointer();
+
+		//! \param ptr the new pointer to change to.
+		LDDocumentPointer& operator= (LDDocument* ptr);
+
+		//! Copy operator.
+		//! \param other the pointer whose internal pointer to copy.
+		inline LDDocumentPointer& operator= (LDDocumentPointer& other)
+		{
+			return operator= (other.pointer());
+		}
+
+		//! Operator overload for a->b support.
+		inline LDDocument* operator->() const
+		{
+			return pointer();
+		}
+
+		//! Cast operator overload
+		inline operator LDDocument*() const
+		{
+			return pointer();
+		}
+
+	private:
+		void addReference();
+		void removeReference();
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc/invokeLater.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,56 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "invokeLater.h"
+#include "../miscallenous.h"
+
+static InvokationDeferer* g_invokationDeferer = new InvokationDeferer();
+
+// =============================================================================
+//
+InvokationDeferer::InvokationDeferer (QObject* parent) : QObject (parent)
+{
+	connect (this, SIGNAL (functionAdded()), this, SLOT (invokeFunctions()),
+		Qt::QueuedConnection);
+}
+
+// =============================================================================
+//
+void InvokationDeferer::addFunctionCall (InvokationDeferer::FunctionType func)
+{
+	m_funcs << func;
+	removeDuplicates (m_funcs);
+	emit functionAdded();
+}
+
+// =============================================================================
+//
+void InvokationDeferer::invokeFunctions()
+{
+	for (FunctionType func : m_funcs)
+		(*func)();
+
+	m_funcs.clear();
+}
+
+// =============================================================================
+//
+void invokeLater (InvokationDeferer::FunctionType func)
+{
+	g_invokationDeferer->addFunctionCall (func);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc/invokeLater.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,42 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QObject>
+
+class InvokationDeferer : public QObject
+{
+	Q_OBJECT
+
+	public:
+		using FunctionType = void(*)();
+
+		explicit InvokationDeferer (QObject* parent = 0);
+		void addFunctionCall (FunctionType func);
+
+	signals:
+		void functionAdded();
+
+	private:
+		QList<FunctionType>	m_funcs;
+
+	private slots:
+		void invokeFunctions();
+};
+
+void invokeLater (InvokationDeferer::FunctionType func);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc/ringFinder.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,169 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ringFinder.h"
+#include "../miscallenous.h"
+
+RingFinder g_RingFinder;
+
+// =============================================================================
+//
+bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution)
+{
+	// Don't recurse too deep.
+	if (m_stack >= 5)
+		return false;
+
+	// Find the scale and number of a ring between r1 and r0.
+	assert (r1 >= r0);
+	double scale = r1 - r0;
+	double num = r0 / scale;
+
+	// If the ring number is integral, we have found a fitting ring to r0 -> r1!
+	if (isInteger (num))
+	{
+		Component cmp;
+		cmp.scale = scale;
+		cmp.num = (int) round (num);
+		currentSolution.addComponent (cmp);
+
+		// If we're still at the first recursion, this is the only
+		// ring and there's nothing left to do. Guess we found the winner.
+		if (m_stack == 0)
+		{
+			m_solutions.push_back (currentSolution);
+			return true;
+		}
+	}
+	else
+	{
+		// Try find solutions by splitting the ring in various positions.
+		if (isZero (r1 - r0))
+			return false;
+
+		double interval;
+
+		// Determine interval. The smaller delta between radii, the more precise
+		// interval should be used. We can't really use a 0.5 increment when
+		// calculating rings to 10 -> 105... that would take ages to process!
+		if (r1 - r0 < 0.5)
+			interval = 0.1;
+		else if (r1 - r0 < 10)
+			interval = 0.5;
+		else if (r1 - r0 < 50)
+			interval = 1;
+		else
+			interval = 5;
+
+		// Now go through possible splits and try find rings for both segments.
+		for (double r = r0 + interval; r < r1; r += interval)
+		{
+			Solution sol = currentSolution;
+
+			m_stack++;
+			bool res = findRingsRecursor (r0, r, sol) && findRingsRecursor (r, r1, sol);
+			m_stack--;
+
+			if (res)
+			{
+				// We succeeded in finding radii for this segment. If the stack is 0, this
+				// is the first recursion to this function. Thus there are no more ring segments
+				// to process and we can add the solution.
+				//
+				// If not, when this function ends, it will be called again with more arguments.
+				// Accept the solution to this segment by setting currentSolution to sol, and
+				// return true to continue processing.
+				if (m_stack == 0)
+					m_solutions.push_back (sol);
+				else
+				{
+					currentSolution = sol;
+					return true;
+				}
+			}
+		}
+
+		return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+// Main function. Call this with r0 and r1. If this returns true, use bestSolution
+// for the solution that was presented.
+//
+bool RingFinder::findRings (double r0, double r1)
+{
+	m_solutions.clear();
+	Solution sol;
+
+	// Recurse in and try find solutions.
+	findRingsRecursor (r0, r1, sol);
+
+	// Compare the solutions and find the best one. The solution class has an operator>
+	// overload to compare two solutions.
+	m_bestSolution = null;
+
+	for (QVector<Solution>::iterator solp = m_solutions.begin(); solp != m_solutions.end(); ++solp)
+	{
+		const Solution& sol = *solp;
+
+		if (m_bestSolution == null || sol.isSuperiorTo (m_bestSolution))
+			m_bestSolution = &sol;
+	}
+
+	return (m_bestSolution != null);
+}
+
+// =============================================================================
+//
+bool RingFinder::Solution::isSuperiorTo (const Solution* other) const
+{
+	// If this solution has less components than the other one, this one
+	// is definitely better.
+	if (getComponents().size() < other->getComponents().size())
+		return true;
+
+	// vice versa
+	if (other->getComponents().size() < getComponents().size())
+		return false;
+
+	// Calculate the maximum ring number. Since the solutions have equal
+	// ring counts, the solutions with lesser maximum rings should result
+	// in cleaner code and less new primitives, right?
+	int maxA = 0,
+		maxB = 0;
+
+	for (int i = 0; i < getComponents().size(); ++i)
+	{
+		maxA = max (getComponents()[i].num, maxA);
+		maxB = max (other->getComponents()[i].num, maxB);
+	}
+
+	if (maxA < maxB)
+		return true;
+
+	if (maxB < maxA)
+		return false;
+
+	// Solutions have equal rings and equal maximum ring numbers. Let's
+	// just say this one is better, at this point it does not matter which
+	// one is chosen.
+	return true;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc/ringFinder.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,129 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "../main.h"
+
+//!
+//! \brief Provides an algorithm for finding solutions of rings between given radii.
+//!
+//! The RingFinder is a class which implements a ring finding algorithm. It is passed
+//! two radii and it tries to find solutions of rings that would fill the given space.
+//!
+//! \note It is not fool-proof and does not always yield a solution, never assume
+//! \note that one is a available as none is guaranteed.
+//!
+class RingFinder
+{
+	public:
+		//! A single component in a solution
+		struct Component
+		{
+			int num;
+			double scale;
+		};
+
+		//! A solution whose components would fill the desired ring space.
+		class Solution
+		{
+			public:
+				//! \returns components of this solution
+				inline const QVector<Component>& getComponents() const
+				{
+					return m_components;
+				}
+
+				//! Add a component to this solution
+				inline void addComponent (const Component& a)
+				{
+					m_components.push_back (a);
+				}
+
+				//! \brief Compare solutions.
+				//!
+				//! Compares this solution with \c other and determines which
+				//! one is superior.
+				//!
+				//! A solution is considered superior if solution has less
+				//! components than the other one. If both solution have an
+				//! equal amount components, the solution with a lesser maximum
+				//! ring number is found superior, as such solutions should
+				//! yield less new primitives and cleaner definitions.
+				//!
+				//! The solution which is found superior to every other solution
+				//! will be the one returned by \c RingFinder::bestSolution().
+				//!
+				//! \param other the solution to check against
+				//! \returns whether this solution is considered superior
+				//! \returns to \c other.
+				//!
+				bool isSuperiorTo (const Solution* other) const;
+
+			private:
+				QVector<Component> m_components;
+		};
+
+		//! Constructs a ring finder.
+		RingFinder() {}
+
+		//! \brief Tries to find rings between \c r0 and \c r1.
+		//!
+		//! This is the main algorithm of the ring finder. It tries to use math
+		//! to find the one ring between r0 and r1. If it fails (the ring number
+		//! is non-integral), it finds an intermediate radius (ceil of the ring
+		//! number times scale) and splits the radius at this point, calling this
+		//! function again to try find the rings between r0 - r and r - r1.
+		//!
+		//! This does not always yield into usable results. If at some point r ==
+		//! r0 or r == r1, there is no hope of finding the rings, at least with
+		//! this algorithm, as it would fall into an infinite recursion.
+		//!
+		//! \param r0 the lower radius of the ring space
+		//! \param r1 the higher radius of the ring space
+		//! \returns whether it was possible to find a solution for the given
+		//! \returns ring space.
+		//!
+		bool findRings (double r0, double r1);
+
+		//! \returns the solution that was considered best. Returns \c null
+		//! \returns if no suitable solution was found.
+		//! \see \c RingFinder::Solution::isSuperiorTo()
+		inline const Solution* bestSolution()
+		{
+			return m_bestSolution;
+		}
+
+		//! \returns all found solutions. The list is empty if no solutions
+		//! \returns were found.
+		inline const QVector<Solution>& allSolutions() const
+		{
+			return m_solutions;
+		}
+
+	private:
+		QVector<Solution> m_solutions;
+		const Solution*   m_bestSolution;
+		int               m_stack;
+
+		//! Helper function for \c findRings
+		bool findRingsRecursor (double r0, double r1, Solution& currentSolution);
+};
+
+extern RingFinder g_RingFinder;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/miscallenous.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,303 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+#include <locale.h>
+#include <QColor>
+#include "main.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "dialogs.h"
+#include "ldDocument.h"
+#include "ui_rotpoint.h"
+#include "misc/documentPointer.cc"
+#include "misc/ringFinder.cc"
+#include "misc/invokeLater.cc"
+
+// Prime number table.
+const int g_primes[NUM_PRIMES] =
+{
+	2,    3,    5,    7,    11,   13,   17,   19,   23,   29,
+	31,   37,   41,   43,   47,   53,   59,   61,   67,   71,
+	73,   79,   83,   89,   97,   101,  103,  107,  109,  113,
+	127,  131,  137,  139,  149,  151,  157,  163,  167,  173,
+	179,  181,  191,  193,  197,  199,  211,  223,  227,  229,
+	233,  239,  241,  251,  257,  263,  269,  271,  277,  281,
+	283,  293,  307,  311,  313,  317,  331,  337,  347,  349,
+	353,  359,  367,  373,  379,  383,  389,  397,  401,  409,
+	419,  421,  431,  433,  439,  443,  449,  457,  461,  463,
+	467,  479,  487,  491,  499,  503,  509,  521,  523,  541,
+	547,  557,  563,  569,  571,  577,  587,  593,  599,  601,
+	607,  613,  617,  619,  631,  641,  643,  647,  653,  659,
+	661,  673,  677,  683,  691,  701,  709,  719,  727,  733,
+	739,  743,  751,  757,  761,  769,  773,  787,  797,  809,
+	811,  821,  823,  827,  829,  839,  853,  857,  859,  863,
+	877,  881,  883,  887,  907,  911,  919,  929,  937,  941,
+	947,  953,  967,  971,  977,  983,  991,  997, 1009, 1013,
+	1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
+	1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151,
+	1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
+	1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
+	1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
+	1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451,
+	1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
+	1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583,
+	1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657,
+	1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733,
+	1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811,
+	1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889,
+	1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987,
+	1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053,
+	2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129,
+	2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213,
+	2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287,
+	2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357,
+	2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423,
+	2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531,
+	2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617,
+	2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687,
+	2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741,
+	2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819,
+	2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903,
+	2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999,
+	3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079,
+	3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181,
+	3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257,
+	3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331,
+	3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413,
+	3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511,
+	3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571,
+};
+
+static const long g_e10[] =
+{
+	1l,
+	10l,
+	100l,
+	1000l,
+	10000l,
+	100000l,
+	1000000l,
+	10000000l,
+	100000000l,
+	1000000000l,
+};
+
+// =============================================================================
+//
+// Grid stuff
+//
+cfg (Int,		grid,					Grid::Medium);
+cfg (Float,		grid_coarse_x,			5.0f);
+cfg (Float,		grid_coarse_y,			5.0f);
+cfg (Float,		grid_coarse_z,			5.0f);
+cfg (Float,		grid_coarse_angle,		45.0f);
+cfg (Float, 	grid_medium_x,			1.0f);
+cfg (Float,		grid_medium_y,			1.0f);
+cfg (Float,		grid_medium_z,			1.0f);
+cfg (Float,		grid_medium_angle,		22.5f);
+cfg (Float,		grid_fine_x,			0.1f);
+cfg (Float,		grid_fine_y,			0.1f);
+cfg (Float,		grid_fine_z,			0.1f);
+cfg (Float,		grid_fine_angle,		7.5f);
+cfg (Int,		edit_rotpoint,			0);
+cfg (Vertex,	edit_customrotpoint,	g_origin);
+
+const gridinfo g_GridInfo[3] =
+{
+	{ "Coarse", { &grid_coarse_x, &grid_coarse_y, &grid_coarse_z, &grid_coarse_angle }},
+	{ "Medium", { &grid_medium_x, &grid_medium_y, &grid_medium_z, &grid_medium_angle }},
+	{ "Fine",   { &grid_fine_x,   &grid_fine_y,   &grid_fine_z,   &grid_fine_angle   }}
+};
+
+// =============================================================================
+//
+// Snap the given coordinate value on the current grid's given axis.
+//
+double Grid::snap (double in, const Grid::Config axis)
+{
+	const double gridval = *currentGrid().confs[axis];
+	const long mult = abs (in / gridval);
+	const bool neg = (in < 0);
+	double out = mult * gridval;
+
+	if (abs<double> (in) - (mult * gridval) > gridval / 2)
+		out += gridval;
+
+	if (neg && out != 0)
+		out *= -1;
+
+	return out;
+}
+
+// =============================================================================
+//
+bool numeric (const QString& tok)
+{
+	bool gotDot = false;
+
+	for (int i = 0; i < tok.length(); ++i)
+	{
+		const QChar c = tok[i];
+
+		// Allow leading hyphen for negatives
+		if (i == 0 && c == '-')
+			continue;
+
+		// Check for decimal point
+		if (!gotDot && c == '.')
+		{
+			gotDot = true;
+			continue;
+		}
+
+		if (c >= '0' && c <= '9')
+			continue; // Digit
+
+		// If the above cases didn't catch this character, it was
+		// illegal and this is therefore not a number.
+		return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+void simplify (int& numer, int& denom)
+{
+	bool repeat;
+
+	do
+	{
+		repeat = false;
+
+		for (int x = 0; x < NUM_PRIMES; x++)
+		{
+			const int prime = g_primes[NUM_PRIMES - x - 1];
+
+			if (numer <= prime || denom <= prime)
+				continue;
+
+			if ((numer % prime == 0) && (denom % prime == 0))
+			{
+				numer /= prime;
+				denom /= prime;
+				repeat = true;
+				break;
+			}
+		}
+	}
+	while (repeat);
+}
+
+// =============================================================================
+//
+Vertex rotPoint (const LDObjectList& objs)
+{
+	switch ((ERotationPoint) edit_rotpoint)
+	{
+		case EObjectOrigin:
+		{
+			LDBoundingBox box;
+
+			// Calculate center vertex
+			for (LDObject* obj : objs)
+			{
+				if (obj->hasMatrix())
+					box << dynamic_cast<LDMatrixObject*> (obj)->position();
+				else
+					box << obj;
+			}
+
+			return box.center();
+		}
+
+		case EWorldOrigin:
+		{
+			return g_origin;
+		}
+
+		case ECustomPoint:
+		{
+			return edit_customrotpoint;
+		}
+	}
+
+	return Vertex();
+}
+
+// =============================================================================
+//
+void configRotationPoint()
+{
+	QDialog* dlg = new QDialog;
+	Ui::RotPointUI ui;
+	ui.setupUi (dlg);
+
+	switch ((ERotationPoint) edit_rotpoint)
+	{
+		case EObjectOrigin:
+			ui.objectPoint->setChecked (true);
+			break;
+
+		case EWorldOrigin:
+			ui.worldPoint->setChecked (true);
+			break;
+
+		case ECustomPoint:
+			ui.customPoint->setChecked (true);
+			break;
+	}
+
+	ui.customX->setValue (edit_customrotpoint.x());
+	ui.customY->setValue (edit_customrotpoint.y());
+	ui.customZ->setValue (edit_customrotpoint.z());
+
+	if (!dlg->exec())
+		return;
+
+	edit_rotpoint =
+		(ui.objectPoint->isChecked()) ? EObjectOrigin :
+		(ui.worldPoint->isChecked())  ? EWorldOrigin :
+		ECustomPoint;
+
+	edit_customrotpoint.x() = ui.customX->value();
+	edit_customrotpoint.y() = ui.customY->value();
+	edit_customrotpoint.z() = ui.customZ->value();
+}
+
+// =============================================================================
+//
+QString join (QList<StringFormatArg> vals, QString delim)
+{
+	QStringList list;
+
+	for (const StringFormatArg& arg : vals)
+		list << arg.text();
+
+	return list.join (delim);
+}
+
+// =============================================================================
+//
+void roundToDecimals (double& a, int decimals)
+{
+	assert (decimals >= 0 && decimals < (signed) (sizeof g_e10 / sizeof *g_e10));
+	a = round (a * g_e10[decimals]) / g_e10[decimals];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/miscallenous.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,138 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QVector>
+#include "configuration.h"
+#include "main.h"
+#include "basics.h"
+
+#define NUM_PRIMES 500
+
+class LDDocument;
+class QColor;
+class QAction;
+
+// Prime numbers
+extern const int g_primes[NUM_PRIMES];
+
+// Returns whether a given string represents a floating point number.
+bool numeric (const QString& tok);
+
+// Simplifies the given fraction.
+void simplify (int& numer, int& denom);
+
+void roundToDecimals (double& a, int decimals);
+
+QString join (QList< StringFormatArg > vals, QString delim = " ");
+
+// Grid stuff
+struct gridinfo
+{
+	const char* const	name;
+	float* const		confs[4];
+};
+
+extern_cfg (Int, grid);
+static const int g_NumGrids = 3;
+extern const gridinfo g_GridInfo[3];
+
+inline const gridinfo& currentGrid()
+{
+	return g_GridInfo[grid];
+}
+
+// =============================================================================
+enum ERotationPoint
+{
+	EObjectOrigin,
+	EWorldOrigin,
+	ECustomPoint
+};
+
+Vertex rotPoint (const LDObjectList& objs);
+void configRotationPoint();
+
+// =============================================================================
+namespace Grid
+{
+	enum Type
+	{
+		Coarse,
+		Medium,
+		Fine
+	};
+
+	enum Config
+	{
+		X,
+		Y,
+		Z,
+		Angle
+	};
+
+	double snap (double value, const Grid::Config axis);
+}
+
+// =============================================================================
+// Plural expression
+template<class T> static inline const char* plural (T n)
+{
+	return (n != 1) ? "s" : "";
+}
+
+// =============================================================================
+// Templated clamp
+template<class T> static inline T clamp (T a, T min, T max)
+{
+	return (a > max) ? max : (a < min) ? min : a;
+}
+
+// Templated minimum
+template<class T> static inline T min (T a, T b)
+{
+	return (a < b) ? a : b;
+}
+
+// Templated maximum
+template<class T> static inline T max (T a, T b)
+{
+	return (a > b) ? a : b;
+}
+
+// Templated absolute value
+template<class T> static inline T abs (T a)
+{
+	return (a >= 0) ? a : -a;
+}
+
+template<class T> inline bool isZero (T a)
+{
+	return abs<T> (a) < 0.0001;
+}
+
+template<class T> inline bool isInteger (T a)
+{
+	return isZero (a - (int) a);
+}
+
+template<class T> void removeDuplicates (QList<T>& a)
+{
+	std::sort (a.begin(), a.end());
+	a.erase (std::unique (a.begin(), a.end()), a.end());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/partDownloader.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,539 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QDir>
+#include <QProgressBar>
+#include <QPushButton>
+#include "partDownloader.h"
+#include "ui_downloadfrom.h"
+#include "basics.h"
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "glRenderer.h"
+#include "configDialog.h"
+
+cfg (String,	net_downloadpath,	"");
+cfg (Bool,		net_guesspaths,	true);
+cfg (Bool,		net_autoclose,		true);
+
+const QString g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/");
+
+// =============================================================================
+//
+void PartDownloader::staticBegin()
+{
+	QString path = getDownloadPath();
+
+	if (path == "" || QDir (path).exists() == false)
+	{
+		critical (PartDownloader::tr ("You need to specify a valid path for "
+			"downloaded files in the configuration to download paths."));
+
+		(new ConfigDialog (ConfigDialog::DownloadTab, null))->exec();
+		return;
+	}
+
+	PartDownloader* dlg = new PartDownloader;
+	dlg->exec();
+}
+
+// =============================================================================
+//
+QString PartDownloader::getDownloadPath()
+{
+	QString path = net_downloadpath;
+
+#if DIRSLASH_CHAR != '/'
+	path.replace (DIRSLASH, "/");
+#endif
+
+	return path;
+}
+
+// =============================================================================
+//
+PartDownloader::PartDownloader (QWidget* parent) : QDialog (parent)
+{
+	setInterface (new Ui_DownloadFrom);
+	interface()->setupUi (this);
+	interface()->fname->setFocus();
+	interface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch);
+
+	setDownloadButton (new QPushButton (tr ("Download")));
+	interface()->buttonBox->addButton (downloadButton(), QDialogButtonBox::ActionRole);
+	getButton (Abort)->setEnabled (false);
+
+	connect (interface()->source, SIGNAL (currentIndexChanged (int)),
+		this, SLOT (sourceChanged (int)));
+	connect (interface()->buttonBox, SIGNAL (clicked (QAbstractButton*)),
+		this, SLOT (buttonClicked (QAbstractButton*)));
+}
+
+// =============================================================================
+//
+PartDownloader::~PartDownloader()
+{
+	delete interface();
+}
+
+// =============================================================================
+//
+QString PartDownloader::getURL() const
+{
+	const Source src = getSource();
+	QString dest;
+
+	switch (src)
+	{
+		case PartsTracker:
+			dest = interface()->fname->text();
+			modifyDestination (dest);
+			return g_unofficialLibraryURL + dest;
+
+		case CustomURL:
+			return interface()->fname->text();
+	}
+
+	// Shouldn't happen
+	return "";
+}
+
+// =============================================================================
+//
+void PartDownloader::modifyDestination (QString& dest) const
+{
+	dest = dest.simplified();
+
+	// If the user doesn't want us to guess, stop right here.
+	if (net_guesspaths == false)
+		return;
+
+	// Ensure .dat extension
+	if (dest.right (4) != ".dat")
+	{
+		// Remove the existing extension, if any. It may be we're here over a
+		// typo in the .dat extension.
+		const int dotpos = dest.lastIndexOf (".");
+
+		if (dotpos != -1 && dotpos >= dest.length() - 4)
+			dest.chop (dest.length() - dotpos);
+
+		dest += ".dat";
+	}
+
+	// If the part starts with s\ or s/, then use parts/s/. Same goes with
+	// 48\ and p/48/.
+	if (dest.left (2) == "s\\" || dest.left (2) == "s/")
+	{
+		dest.remove (0, 2);
+		dest.prepend ("parts/s/");
+	} elif (dest.left (3) == "48\\" || dest.left (3) == "48/")
+	{
+		dest.remove (0, 3);
+		dest.prepend ("p/48/");
+	}
+
+	/* Try determine where to put this part. We have four directories:
+	   parts/, parts/s/, p/, and p/48/. If we haven't already specified
+	   either parts/ or p/, we need to add it automatically. Part files
+	   are numbers wit a possible u prefix for parts with unknown number
+	   which can be followed by any of:
+	   - c** (composites)
+	   - d** (formed stickers)
+	   - p** (patterns)
+	   - a lowercase alphabetic letter for variants
+
+	   Subfiles (usually) have an s** prefix, in which case we use parts/s/.
+	   Note that the regex starts with a '^' so it won't catch already fully
+	   given part file names. */
+	QString partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*";
+	QString subpartRegex = partRegex + "s[0-9][0-9]+";
+
+	partRegex += "\\.dat$";
+	subpartRegex += "\\.dat$";
+
+	if (QRegExp (subpartRegex).exactMatch (dest))
+		dest.prepend ("parts/s/");
+	elif (QRegExp (partRegex).exactMatch (dest))
+		dest.prepend ("parts/");
+	elif (dest.left (6) != "parts/" && dest.left (2) != "p/")
+		dest.prepend ("p/");
+}
+
+// =============================================================================
+//
+PartDownloader::Source PartDownloader::getSource() const
+{
+	return (Source) interface()->source->currentIndex();
+}
+
+// =============================================================================
+//
+void PartDownloader::sourceChanged (int i)
+{
+	if (i == CustomURL)
+		interface()->fileNameLabel->setText (tr ("URL:"));
+	else
+		interface()->fileNameLabel->setText (tr ("File name:"));
+}
+
+// =============================================================================
+//
+void PartDownloader::buttonClicked (QAbstractButton* btn)
+{
+	if (btn == getButton (Close))
+	{
+		reject();
+	}
+	elif (btn == getButton (Abort))
+	{
+		setAborted (true);
+
+		for (PartDownloadRequest* req : requests())
+			req->abort();
+	}
+	elif (btn == getButton (Download))
+	{
+		QString dest = interface()->fname->text();
+		setPrimaryFile (null);
+		setAborted (false);
+
+		if (getSource() == CustomURL)
+			dest = basename (getURL());
+
+		modifyDestination (dest);
+
+		if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest))
+		{
+			const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest);
+			if (!confirm (tr ("Overwrite?"), overwritemsg))
+				return;
+		}
+
+		downloadButton()->setEnabled (false);
+		interface()->progress->setEnabled (true);
+		interface()->fname->setEnabled (false);
+		interface()->source->setEnabled (false);
+		downloadFile (dest, getURL(), true);
+		getButton (Close)->setEnabled (false);
+		getButton (Abort)->setEnabled (true);
+		getButton (Download)->setEnabled (false);
+	}
+}
+
+// =============================================================================
+//
+void PartDownloader::downloadFile (QString dest, QString url, bool primary)
+{
+	const int row = interface()->progress->rowCount();
+
+	// Don't download files repeadetly.
+	if (filesToDownload().indexOf (dest) != -1)
+		return;
+
+	modifyDestination (dest);
+	PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this);
+	m_filesToDownload << dest;
+	m_requests << req;
+	interface()->progress->insertRow (row);
+	req->setTableRow (row);
+	req->updateToTable();
+}
+
+// =============================================================================
+//
+void PartDownloader::checkIfFinished()
+{
+	bool failed = isAborted();
+
+	// If there is some download still working, we're not finished.
+	for (PartDownloadRequest* req : requests())
+	{
+		if (!req->isFinished())
+			return;
+
+		if (req->state() == PartDownloadRequest::EFailed)
+			failed = true;
+	}
+
+	for (PartDownloadRequest* req : requests())
+		delete req;
+
+	m_requests.clear();
+
+	// Update everything now
+	if (primaryFile() != null)
+	{
+		LDDocument::setCurrent (primaryFile());
+		reloadAllSubfiles();
+		g_win->doFullRefresh();
+		g_win->R()->resetAngles();
+	}
+
+	if (net_autoclose && !failed)
+	{
+		// Close automatically if desired.
+		accept();
+	}
+	else
+	{
+		// Allow the prompt be closed now.
+		getButton (Abort)->setEnabled (false);
+		getButton (Close)->setEnabled (true);
+	}
+}
+
+// =============================================================================
+//
+QPushButton* PartDownloader::getButton (PartDownloader::Button i)
+{
+	switch (i)
+	{
+		case Download:
+			return downloadButton();
+
+		case Abort:
+			return qobject_cast<QPushButton*> (interface()->buttonBox->button (QDialogButtonBox::Abort));
+
+		case Close:
+			return qobject_cast<QPushButton*> (interface()->buttonBox->button (QDialogButtonBox::Close));
+	}
+
+	return null;
+}
+
+// =============================================================================
+//
+PartDownloadRequest::PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent) :
+	QObject (parent),
+	m_state (ERequesting),
+	m_prompt (parent),
+	m_url (url),
+	m_destinaton (dest),
+	m_filePath (PartDownloader::getDownloadPath() + DIRSLASH + dest),
+	m_networkManager (new QNetworkAccessManager),
+	m_isFirstUpdate (true),
+	m_isPrimary (primary),
+	m_filePointer (null)
+{
+	// Make sure that we have a valid destination.
+	QString dirpath = dirname (filePath());
+
+	QDir dir (dirpath);
+
+	if (dir.exists() == false)
+	{
+		print ("Creating %1...\n", dirpath);
+
+		if (!dir.mkpath (dirpath))
+			critical (format (tr ("Couldn't create the directory %1!"), dirpath));
+	}
+
+	setNetworkReply (networkManager()->get (QNetworkRequest (QUrl (url))));
+	connect (networkReply(), SIGNAL (finished()), this, SLOT (downloadFinished()));
+	connect (networkReply(), SIGNAL (readyRead()), this, SLOT (readyRead()));
+	connect (networkReply(), SIGNAL (downloadProgress (qint64, qint64)),
+		this, SLOT (downloadProgress (qint64, qint64)));
+}
+
+// =============================================================================
+//
+PartDownloadRequest::~PartDownloadRequest() {}
+
+// =============================================================================
+//
+void PartDownloadRequest::updateToTable()
+{
+	const int		labelcol = PartDownloader::PartLabelColumn,
+					progcol = PartDownloader::ProgressColumn;
+	QTableWidget*	table = prompt()->interface()->progress;
+	QProgressBar*	prog;
+
+	switch (state())
+	{
+		case ERequesting:
+		case EDownloading:
+		{
+			prog = qobject_cast<QProgressBar*> (table->cellWidget (tableRow(), progcol));
+
+			if (!prog)
+			{
+				prog = new QProgressBar;
+				table->setCellWidget (tableRow(), progcol, prog);
+			}
+
+			prog->setRange (0, numBytesTotal());
+			prog->setValue (numBytesRead());
+		} break;
+
+		case EFinished:
+		case EFailed:
+		{
+			const QString text = (state() == EFinished)
+				? "<b><span style=\"color: #080\">FINISHED</span></b>"
+				: "<b><span style=\"color: #800\">FAILED</span></b>";
+
+			QLabel* lb = new QLabel (text);
+			lb->setAlignment (Qt::AlignCenter);
+			table->setCellWidget (tableRow(), progcol, lb);
+		} break;
+	}
+
+	QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (tableRow(), labelcol));
+
+	if (isFirstUpdate())
+	{
+		lb = new QLabel (format ("<b>%1</b>", destinaton()), table);
+		table->setCellWidget (tableRow(), labelcol, lb);
+	}
+
+	// Make sure that the cell is big enough to contain the label
+	if (table->columnWidth (labelcol) < lb->width())
+		table->setColumnWidth (labelcol, lb->width());
+
+	setFirstUpdate (true);
+}
+
+// =============================================================================
+//
+void PartDownloadRequest::downloadFinished()
+{
+	if (networkReply()->error() != QNetworkReply::NoError)
+	{
+		if (isPrimary() && !prompt()->isAborted())
+			critical (networkReply()->errorString());
+
+		setState (EFailed);
+	}
+	elif (state() != EFailed)
+		setState (EFinished);
+
+	setNumBytesRead (numBytesTotal());
+	updateToTable();
+
+	if (filePointer())
+	{
+		filePointer()->close();
+		delete filePointer();
+		setFilePointer (null);
+
+		if (state() == EFailed)
+			QFile::remove (filePath());
+	}
+
+	if (state() != EFinished)
+	{
+		prompt()->checkIfFinished();
+		return;
+	}
+
+	// Try to load this file now.
+	LDDocument* f = openDocument (filePath(), false);
+
+	if (!f)
+		return;
+
+	f->setImplicit (!isPrimary());
+
+	// Iterate through this file and check for errors. If there's any that stems
+	// from unknown file references, try resolve that by downloading the reference.
+	// This is why downloading a part may end up downloading multiple files, as
+	// it resolves dependencies.
+	for (LDObject* obj : f->objects())
+	{
+		LDError* err = dynamic_cast<LDError*> (obj);
+
+		if (err == null || err->fileReferenced().isEmpty())
+			continue;
+
+		QString dest = err->fileReferenced();
+		prompt()->modifyDestination (dest);
+		prompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false);
+	}
+
+	if (isPrimary())
+	{
+		addRecentFile (filePath());
+		prompt()->setPrimaryFile (f);
+	}
+
+	prompt()->checkIfFinished();
+}
+
+// =============================================================================
+//
+void PartDownloadRequest::downloadProgress (int64 recv, int64 total)
+{
+	setNumBytesRead (recv);
+	setNumBytesTotal (total);
+	setState (EDownloading);
+	updateToTable();
+}
+
+// =============================================================================
+//
+void PartDownloadRequest::readyRead()
+{
+	if (state() == EFailed)
+		return;
+
+	if (filePointer() == null)
+	{
+		m_filePath.replace ("\\", "/");
+
+		// We have already asked the user whether we can overwrite so we're good
+		// to go here.
+		setFilePointer (new QFile (filePath().toLocal8Bit()));
+
+		if (!filePointer()->open (QIODevice::WriteOnly))
+		{
+			critical (format (tr ("Couldn't open %1 for writing: %2"), filePath(), strerror (errno)));
+			setState (EFailed);
+			networkReply()->abort();
+			updateToTable();
+			prompt()->checkIfFinished();
+			return;
+		}
+	}
+
+	filePointer()->write (networkReply()->readAll());
+}
+
+// =============================================================================
+//
+bool PartDownloadRequest::isFinished() const
+{
+	return state() == EFinished || state() == EFailed;
+}
+
+// =============================================================================
+//
+void PartDownloadRequest::abort()
+{
+	networkReply()->abort();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (DownloadFrom, 0)
+{
+	PartDownloader::staticBegin();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/partDownloader.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,127 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QDialog>
+#include "main.h"
+#include "basics.h"
+
+class LDDocument;
+class QFile;
+class PartDownloadRequest;
+class Ui_DownloadFrom;
+class QNetworkAccessManager;
+class QNetworkRequest;
+class QNetworkReply;
+class QAbstractButton;
+
+// =============================================================================
+//
+class PartDownloader : public QDialog
+{
+	public:
+		enum Source
+		{
+			PartsTracker,
+			CustomURL,
+		};
+
+		enum Button
+		{
+			Download,
+			Abort,
+			Close
+		};
+
+		enum TableColumn
+		{
+			PartLabelColumn,
+			ProgressColumn,
+		};
+
+		using RequestList = QList<PartDownloadRequest*>;
+
+		Q_OBJECT
+		PROPERTY (public,	LDDocument*, 		primaryFile,		setPrimaryFile,		STOCK_WRITE)
+		PROPERTY (public,	bool,				isAborted,			setAborted,			STOCK_WRITE)
+		PROPERTY (private,	Ui_DownloadFrom*,	interface,			setInterface,		STOCK_WRITE)
+		PROPERTY (private,	QStringList,		filesToDownload,	setFilesToDownload,	STOCK_WRITE)
+		PROPERTY (private,	RequestList,		requests,			setRequests,		STOCK_WRITE)
+		PROPERTY (private,	QPushButton*,		downloadButton,		setDownloadButton,	STOCK_WRITE)
+
+	public:
+		explicit		PartDownloader (QWidget* parent = null);
+		virtual			~PartDownloader();
+
+		void			downloadFile (QString dest, QString url, bool primary);
+		QPushButton*	getButton (Button i);
+		QString			getURL() const;
+		Source			getSource() const;
+		void			modifyDestination (QString& dest) const;
+
+		static QString	getDownloadPath();
+		static void		staticBegin();
+
+	public slots:
+		void			buttonClicked (QAbstractButton* btn);
+		void			checkIfFinished();
+		void			sourceChanged (int i);
+};
+
+// =============================================================================
+//
+class PartDownloadRequest : public QObject
+{
+	public:
+		enum EState
+		{
+			ERequesting,
+			EDownloading,
+			EFinished,
+			EFailed,
+		};
+
+		Q_OBJECT
+		PROPERTY (public,	int,					tableRow,		setTableRow,		STOCK_WRITE)
+		PROPERTY (private,	EState,					state,			setState,			STOCK_WRITE)
+		PROPERTY (private,	PartDownloader*,		prompt,			setPrompt,			STOCK_WRITE)
+		PROPERTY (private,	QString,				url,			setURL,				STOCK_WRITE)
+		PROPERTY (private,	QString,				destinaton,		setDestination,		STOCK_WRITE)
+		PROPERTY (private,	QString,				filePath,		setFilePath,		STOCK_WRITE)
+		PROPERTY (private,	QNetworkAccessManager*,	networkManager,	setNetworkManager,	STOCK_WRITE)
+		PROPERTY (private,	QNetworkReply*,			networkReply,	setNetworkReply,	STOCK_WRITE)
+		PROPERTY (private,	bool,					isFirstUpdate,	setFirstUpdate,		STOCK_WRITE)
+		PROPERTY (private,	int64,					numBytesRead,	setNumBytesRead,	STOCK_WRITE)
+		PROPERTY (private,	int64,					numBytesTotal,	setNumBytesTotal,	STOCK_WRITE)
+		PROPERTY (private,	bool,					isPrimary,		setPrimary,			STOCK_WRITE)
+		PROPERTY (private,	QFile*,					filePointer,	setFilePointer,		STOCK_WRITE)
+
+	public:
+		explicit PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent);
+		PartDownloadRequest (const PartDownloadRequest&) = delete;
+		virtual ~PartDownloadRequest();
+		void updateToTable();
+		bool isFinished() const;
+		void operator= (const PartDownloadRequest&) = delete;
+
+	public slots:
+		void downloadFinished();
+		void readyRead();
+		void downloadProgress (qint64 recv, qint64 total);
+		void abort();
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/primitives.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,703 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDir>
+#include <QRegExp>
+#include <QFileDialog>
+#include "ldDocument.h"
+#include "mainWindow.h"
+#include "primitives.h"
+#include "ui_makeprim.h"
+#include "miscallenous.h"
+#include "colors.h"
+
+QList<PrimitiveCategory*> g_PrimitiveCategories;
+QList<Primitive> g_primitives;
+static PrimitiveScanner* g_activeScanner = null;
+PrimitiveCategory* g_unmatched = null;
+
+extern_cfg (String, ld_defaultname);
+extern_cfg (String, ld_defaultuser);
+extern_cfg (Int, ld_defaultlicense);
+
+static const QStringList g_radialNameRoots =
+{
+	"edge",
+	"cyli",
+	"disc",
+	"ndis",
+	"ring",
+	"con"
+};
+
+PrimitiveScanner* getActivePrimitiveScanner()
+{
+	return g_activeScanner;
+}
+
+// =============================================================================
+//
+void loadPrimitives()
+{
+	PrimitiveCategory::loadCategories();
+
+	// Try to load prims.cfg
+	QFile conf (Config::filepath ("prims.cfg"));
+
+	if (conf.open (QIODevice::ReadOnly) == false)
+	{
+		// No prims.cfg, build it
+		PrimitiveScanner::start();
+	}
+	else
+	{
+		while (conf.atEnd() == false)
+		{
+			QString line = conf.readLine();
+
+			if (line.endsWith ("\n"))
+				line.chop (1);
+
+			int space = line.indexOf (" ");
+
+			if (space == -1)
+				continue;
+
+			Primitive info;
+			info.name = line.left (space);
+			info.title = line.mid (space + 1);
+			g_primitives << info;
+		}
+
+		PrimitiveCategory::populateCategories();
+		print ("%1 primitives loaded.\n", g_primitives.size());
+	}
+}
+
+// =============================================================================
+//
+static void recursiveGetFilenames (QDir dir, QList<QString>& fnames)
+{
+	QFileInfoList flist = dir.entryInfoList (QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
+
+	for (const QFileInfo& info : flist)
+	{
+		if (info.isDir())
+			recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames);
+		else
+			fnames << info.absoluteFilePath();
+	}
+}
+
+// =============================================================================
+//
+PrimitiveScanner::PrimitiveScanner (QObject* parent) :
+	QObject (parent),
+	m_i (0)
+{
+	g_activeScanner = this;
+	QDir dir (LDPaths::prims());
+	assert (dir.exists());
+	m_baselen = dir.absolutePath().length();
+	recursiveGetFilenames (dir, m_files);
+	emit starting (m_files.size());
+	print ("Scanning primitives...");
+}
+
+// =============================================================================
+//
+PrimitiveScanner::~PrimitiveScanner()
+{
+	g_activeScanner = null;
+}
+
+// =============================================================================
+//
+void PrimitiveScanner::work()
+{
+	int j = min (m_i + 100, m_files.size());
+
+	for (; m_i < j; ++m_i)
+	{
+		QString fname = m_files[m_i];
+		QFile f (fname);
+
+		if (!f.open (QIODevice::ReadOnly))
+			continue;
+
+		Primitive info;
+		info.name = fname.mid (m_baselen + 1);  // make full path relative
+		info.name.replace ('/', '\\');  // use DOS backslashes, they're expected
+		info.category = null;
+		QByteArray titledata = f.readLine();
+
+		if (titledata != QByteArray())
+			info.title = QString::fromUtf8 (titledata);
+
+		info.title = info.title.simplified();
+
+		if (Q_LIKELY (info.title[0] == '0'))
+		{
+			info.title.remove (0, 1);  // remove 0
+			info.title = info.title.simplified();
+		}
+
+		m_prims << info;
+	}
+
+	if (m_i == m_files.size())
+	{
+		// Done with primitives, now save to a config file
+		QString path = Config::filepath ("prims.cfg");
+		QFile conf (path);
+
+		if (!conf.open (QIODevice::WriteOnly | QIODevice::Text))
+			critical (format ("Couldn't write primitive list %1: %2",
+				path, conf.errorString()));
+		else
+		{
+			for (Primitive& info : m_prims)
+				fprint (conf, "%1 %2\r\n", info.name, info.title);
+
+			conf.close();
+		}
+
+		g_primitives = m_prims;
+		PrimitiveCategory::populateCategories();
+		print ("%1 primitives scanned", g_primitives.size());
+		g_activeScanner = null;
+		emit workDone();
+		deleteLater();
+	}
+	else
+	{
+		// Defer to event loop, pick up the work later
+		emit update (m_i);
+		QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection);
+	}
+}
+
+// =============================================================================
+//
+void PrimitiveScanner::start()
+{
+	if (g_activeScanner)
+		return;
+
+	PrimitiveScanner* scanner = new PrimitiveScanner;
+	scanner->work();
+}
+
+// =============================================================================
+//
+PrimitiveCategory::PrimitiveCategory (QString name, QObject* parent) :
+	QObject (parent),
+	m_name (name) {}
+
+// =============================================================================
+//
+void PrimitiveCategory::populateCategories()
+{
+	for (PrimitiveCategory* cat : g_PrimitiveCategories)
+		cat->prims.clear();
+
+
+	for (Primitive& prim : g_primitives)
+	{
+		bool matched = false;
+		prim.category = null;
+
+		// Go over the categories and their regexes, if and when there's a match,
+		// the primitive's category is set to the category the regex beloings to.
+		for (PrimitiveCategory* cat : g_PrimitiveCategories)
+		{
+			for (RegexEntry& entry : cat->regexes)
+			{
+				switch (entry.type)
+				{
+					case EFilenameRegex:
+					{
+						// f-regex, check against filename
+						matched = entry.regex.exactMatch (prim.name);
+					} break;
+
+					case ETitleRegex:
+					{
+						// t-regex, check against title
+						matched = entry.regex.exactMatch (prim.title);
+					} break;
+				}
+
+				if (matched)
+				{
+					prim.category = cat;
+					break;
+				}
+			}
+
+			// Drop out if a category was decided on.
+			if (prim.category != null)
+				break;
+		}
+
+		// If there was a match, add the primitive to the category.
+		// Otherwise, add it to the list of unmatched primitives.
+		if (prim.category != null)
+			prim.category->prims << prim;
+		else
+			g_unmatched->prims << prim;
+	}
+}
+
+// =============================================================================
+//
+void PrimitiveCategory::loadCategories()
+{
+	for (PrimitiveCategory* cat : g_PrimitiveCategories)
+		delete cat;
+
+	g_PrimitiveCategories.clear();
+	QString path = Config::dirpath() + "primregexps.cfg";
+
+	if (!QFile::exists (path))
+		path = ":/data/primitive-categories.cfg";
+
+	QFile f (path);
+
+	if (!f.open (QIODevice::ReadOnly))
+	{
+		critical (format (QObject::tr ("Failed to open primitive categories: %1"), f.errorString()));
+		return;
+	}
+
+	PrimitiveCategory* cat = null;
+
+	while (f.atEnd() == false)
+	{
+		QString line = f.readLine();
+		int colon;
+
+		if (line.endsWith ("\n"))
+			line.chop (1);
+
+		if (line.length() == 0 || line[0] == '#')
+			continue;
+
+		if ((colon = line.indexOf (":")) == -1)
+		{
+			if (cat && cat->isValidToInclude())
+				g_PrimitiveCategories << cat;
+
+			cat = new PrimitiveCategory (line);
+		}
+		elif (cat != null)
+		{
+			QString cmd = line.left (colon);
+			RegexType type = EFilenameRegex;
+
+			if (cmd == "f")
+				type = EFilenameRegex;
+			elif (cmd == "t")
+				type = ETitleRegex;
+			else
+			{
+				print (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line);
+				continue;
+			}
+
+			QRegExp regex (line.mid (colon + 1));
+			RegexEntry entry = { regex, type };
+			cat->regexes << entry;
+		}
+		else
+			print ("Warning: Rules given before the first category name");
+	}
+
+	if (cat->isValidToInclude())
+		g_PrimitiveCategories << cat;
+
+	// Add a category for unmatched primitives.
+	// Note: if this function is called the second time, g_unmatched has been
+	// deleted at the beginning of the function and is dangling at this point.
+	g_unmatched = new PrimitiveCategory (tr ("Other"));
+	g_PrimitiveCategories << g_unmatched;
+	f.close();
+}
+
+// =============================================================================
+//
+bool PrimitiveCategory::isValidToInclude()
+{
+	if (regexes.isEmpty())
+	{
+		print (tr ("Warning: category \"%1\" left without patterns"), name());
+		deleteLater();
+		return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+bool isPrimitiveLoaderBusy()
+{
+	return g_activeScanner != null;
+}
+
+// =============================================================================
+//
+static double radialPoint (int i, int divs, double (*func) (double))
+{
+	return (*func) ((i * 2 * pi) / divs);
+}
+
+// =============================================================================
+//
+void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines)
+{
+	for (int i = 0; i < segs; ++i)
+	{
+		double x0 = radius * radialPoint (i, divs, cos),
+			x1 = radius * radialPoint (i + 1, divs, cos),
+			z0 = radius * radialPoint (i, divs, sin),
+			z1 = radius * radialPoint (i + 1, divs, sin);
+
+		lines << QLineF (QPointF (x0, z0), QPointF (x1, z1));
+	}
+}
+
+// =============================================================================
+//
+LDObjectList makePrimitive (PrimitiveType type, int segs, int divs, int num)
+{
+	LDObjectList objs;
+	QList<int> condLineSegs;
+	QList<QLineF> circle;
+
+	makeCircle (segs, divs, 1, circle);
+
+	for (int i = 0; i < segs; ++i)
+	{
+		double x0 = circle[i].x1(),
+				   x1 = circle[i].x2(),
+				   z0 = circle[i].y1(),
+				   z1 = circle[i].y2();
+
+		switch (type)
+		{
+			case Circle:
+			{
+				Vertex v0 (x0, 0.0f, z0),
+				  v1 (x1, 0.0f, z1);
+
+				LDLine* line = new LDLine;
+				line->setVertex (0, v0);
+				line->setVertex (1, v1);
+				line->setColor (edgecolor);
+				objs << line;
+			} break;
+
+			case Cylinder:
+			case Ring:
+			case Cone:
+			{
+				double x2, x3, z2, z3;
+				double y0, y1, y2, y3;
+
+				if (type == Cylinder)
+				{
+					x2 = x1;
+					x3 = x0;
+					z2 = z1;
+					z3 = z0;
+
+					y0 = y1 = 0.0f;
+					y2 = y3 = 1.0f;
+				}
+				else
+				{
+					x2 = x1 * (num + 1);
+					x3 = x0 * (num + 1);
+					z2 = z1 * (num + 1);
+					z3 = z0 * (num + 1);
+
+					x0 *= num;
+					x1 *= num;
+					z0 *= num;
+					z1 *= num;
+
+					if (type == Ring)
+						y0 = y1 = y2 = y3 = 0.0f;
+					else
+					{
+						y0 = y1 = 1.0f;
+						y2 = y3 = 0.0f;
+					}
+				}
+
+				Vertex v0 (x0, y0, z0),
+					   v1 (x1, y1, z1),
+					   v2 (x2, y2, z2),
+					   v3 (x3, y3, z3);
+
+				LDQuad* quad = new LDQuad;
+				quad->setColor (maincolor);
+				quad->setVertex (0, v0);
+				quad->setVertex (1, v1);
+				quad->setVertex (2, v2);
+				quad->setVertex (3, v3);
+
+				if (type == Cylinder)
+					quad->invert();
+
+				objs << quad;
+
+				if (type == Cylinder || type == Cone)
+					condLineSegs << i;
+			} break;
+
+			case Disc:
+			case DiscNeg:
+			{
+				double x2, z2;
+
+				if (type == Disc)
+					x2 = z2 = 0.0f;
+				else
+				{
+					x2 = (x0 >= 0.0f) ? 1.0f : -1.0f;
+					z2 = (z0 >= 0.0f) ? 1.0f : -1.0f;
+				}
+
+				Vertex v0 (x0, 0.0f, z0),
+					   v1 (x1, 0.0f, z1),
+					   v2 (x2, 0.0f, z2);
+
+				// Disc negatives need to go the other way around, otherwise
+				// they'll end up upside-down.
+				LDTriangle* seg = new LDTriangle;
+				seg->setColor (maincolor);
+				seg->setVertex (type == Disc ? 0 : 2, v0);
+				seg->setVertex (1, v1);
+				seg->setVertex (type == Disc ? 2 : 0, v2);
+				objs << seg;
+			} break;
+		}
+	}
+
+	// If this is not a full circle, we need a conditional line at the other
+	// end, too.
+	if (segs < divs && condLineSegs.size() != 0)
+		condLineSegs << segs;
+
+	for (int i : condLineSegs)
+	{
+		Vertex v0 (radialPoint (i, divs, cos), 0.0f, radialPoint (i, divs, sin)),
+		  v1,
+		  v2 (radialPoint (i + 1, divs, cos), 0.0f, radialPoint (i + 1, divs, sin)),
+		  v3 (radialPoint (i - 1, divs, cos), 0.0f, radialPoint (i - 1, divs, sin));
+
+		if (type == Cylinder)
+			v1 = Vertex (v0[X], 1.0f, v0[Z]);
+		elif (type == Cone)
+		{
+			v1 = Vertex (v0[X] * (num + 1), 0.0f, v0[Z] * (num + 1));
+			v0[X] *= num;
+			v0[Y] = 1.0f;
+			v0[Z] *= num;
+		}
+
+		LDCondLine* line = new LDCondLine;
+		line->setColor (edgecolor);
+		line->setVertex (0, v0);
+		line->setVertex (1, v1);
+		line->setVertex (2, v2);
+		line->setVertex (3, v3);
+		objs << line;
+	}
+
+	return objs;
+}
+
+// =============================================================================
+//
+static QString primitiveTypeName (PrimitiveType type)
+{
+	// Not translated as primitives are in English.
+	return type == Circle   ? "Circle" :
+		   type == Cylinder ? "Cylinder" :
+		   type == Disc     ? "Disc" :
+		   type == DiscNeg  ? "Disc Negative" :
+		   type == Ring     ? "Ring" : "Cone";
+}
+
+// =============================================================================
+//
+QString radialFileName (PrimitiveType type, int segs, int divs, int num)
+{
+	int numer = segs,
+			denom = divs;
+
+	// Simplify the fractional part, but the denominator must be at least 4.
+	simplify (numer, denom);
+
+	if (denom < 4)
+	{
+		const int factor = 4 / denom;
+		numer *= factor;
+		denom *= factor;
+	}
+
+	// Compose some general information: prefix, fraction, root, ring number
+	QString prefix = (divs == g_lores) ? "" : format ("%1/", divs);
+	QString frac = format ("%1-%2", numer, denom);
+	QString root = g_radialNameRoots[type];
+	QString numstr = (type == Ring || type == Cone) ? format ("%1", num) : "";
+
+	// Truncate the root if necessary (7-16rin4.dat for instance).
+	// However, always keep the root at least 2 characters.
+	int extra = (frac.length() + numstr.length() + root.length()) - 8;
+	root.chop (clamp (extra, 0, 2));
+
+	// Stick them all together and return the result.
+	return prefix + frac + root + numstr + ".dat";
+}
+
+// =============================================================================
+//
+LDDocument* generatePrimitive (PrimitiveType type, int segs, int divs, int num)
+{
+	// Make the description
+	QString frac = QString::number ((float) segs / divs);
+	QString name = radialFileName (type, segs, divs, num);
+	QString descr;
+
+	// Ensure that there's decimals, even if they're 0.
+	if (frac.indexOf (".") == -1)
+		frac += ".0";
+
+	if (type == Ring || type == Cone)
+	{
+		QString spacing =
+			(num < 10) ? "  " :
+			(num < 100) ? " "  : "";
+
+		descr = format ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac);
+	}
+	else
+		descr = format ("%1 %2", primitiveTypeName (type), frac);
+
+	// Prepend "Hi-Res" if 48/ primitive.
+	if (divs == g_hires)
+		descr.insert (0, "Hi-Res ");
+
+	LDDocument* f = new LDDocument;
+	f->setDefaultName (name);
+
+	QString author = APPNAME;
+	QString license = "";
+
+	if (ld_defaultname.isEmpty() == false)
+	{
+		license = getLicenseText (ld_defaultlicense);
+		author = format ("%1 [%2]", ld_defaultname, ld_defaultuser);
+	}
+
+	f->addObjects (
+	{
+		new LDComment (descr),
+		new LDComment (format ("Name: %1", name)),
+		new LDComment (format ("Author: %1", author)),
+		new LDComment (format ("!LDRAW_ORG Unofficial_%1Primitive", divs == g_hires ? "48_" : "")),
+		new LDComment (license),
+		new LDEmpty,
+		new LDBFC (LDBFC::CertifyCCW),
+		new LDEmpty,
+	});
+
+	f->addObjects (makePrimitive (type, segs, divs, num));
+	return f;
+}
+
+// =============================================================================
+//
+LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num)
+{
+	QString name = radialFileName (type, segs, divs, num);
+	LDDocument* f = getDocument (name);
+
+	if (f != null)
+		return f;
+
+	return generatePrimitive (type, segs, divs, num);
+}
+
+// =============================================================================
+//
+PrimitivePrompt::PrimitivePrompt (QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f)
+{
+	ui = new Ui_MakePrimUI;
+	ui->setupUi (this);
+	connect (ui->cb_hires, SIGNAL (toggled (bool)), this, SLOT (hiResToggled (bool)));
+}
+
+// =============================================================================
+//
+PrimitivePrompt::~PrimitivePrompt()
+{
+	delete ui;
+}
+
+// =============================================================================
+//
+void PrimitivePrompt::hiResToggled (bool on)
+{
+	ui->sb_segs->setMaximum (on ? g_hires : g_lores);
+
+	// If the current value is 16 and we switch to hi-res, default the
+	// spinbox to 48.
+	if (on && ui->sb_segs->value() == g_lores)
+		ui->sb_segs->setValue (g_hires);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (MakePrimitive, 0)
+{
+	PrimitivePrompt* dlg = new PrimitivePrompt (g_win);
+
+	if (!dlg->exec())
+		return;
+
+	int segs = dlg->ui->sb_segs->value();
+	int divs = dlg->ui->cb_hires->isChecked() ? g_hires : g_lores;
+	int num = dlg->ui->sb_ringnum->value();
+	PrimitiveType type =
+		dlg->ui->rb_circle->isChecked()   ? Circle :
+		dlg->ui->rb_cylinder->isChecked() ? Cylinder :
+		dlg->ui->rb_disc->isChecked()     ? Disc :
+		dlg->ui->rb_ndisc->isChecked()    ? DiscNeg :
+		dlg->ui->rb_ring->isChecked()     ? Ring : Cone;
+
+	LDDocument* f = generatePrimitive (type, segs, divs, num);
+
+	g_win->save (f, false);
+	delete f;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/primitives.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,130 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include "main.h"
+#include "basics.h"
+#include <QRegExp>
+#include <QDialog>
+
+class LDDocument;
+class Ui_MakePrimUI;
+class PrimitiveCategory;
+struct Primitive
+{
+	QString				name,
+						title;
+	PrimitiveCategory*	category;
+};
+
+class PrimitiveCategory : public QObject
+{
+	Q_OBJECT
+	PROPERTY (public, QString, name, setName, STOCK_WRITE)
+
+	public:
+		enum RegexType
+		{
+			EFilenameRegex,
+			ETitleRegex
+		};
+
+		struct RegexEntry
+		{
+			QRegExp		regex;
+			RegexType	type;
+		};
+
+		QList<RegexEntry> regexes;
+		QList<Primitive> prims;
+
+		explicit PrimitiveCategory (QString name, QObject* parent = 0);
+		bool isValidToInclude();
+
+		static void loadCategories();
+		static void populateCategories();
+};
+
+// =============================================================================
+//
+// PrimitiveScanner
+//
+// Worker object that scans the primitives folder for primitives and
+// builds an index of them.
+//
+class PrimitiveScanner : public QObject
+{
+	Q_OBJECT
+
+	public:
+		explicit			PrimitiveScanner (QObject* parent = 0);
+		virtual				~PrimitiveScanner();
+		static void			start();
+
+	public slots:
+		void				work();
+
+	signals:
+		void				starting (int num);
+		void				workDone();
+		void				update (int i);
+
+	private:
+		QList<Primitive>	m_prims;
+		QStringList			m_files;
+		int					m_i;
+		int					m_baselen;
+};
+
+extern QList<PrimitiveCategory*> g_PrimitiveCategories;
+
+void loadPrimitives();
+PrimitiveScanner* getActivePrimitiveScanner();
+
+enum PrimitiveType
+{
+	Circle,
+	Cylinder,
+	Disc,
+	DiscNeg,
+	Ring,
+	Cone,
+};
+
+// =============================================================================
+class PrimitivePrompt : public QDialog
+{
+	Q_OBJECT
+
+	public:
+		explicit PrimitivePrompt (QWidget* parent = null, Qt::WindowFlags f = 0);
+		virtual ~PrimitivePrompt();
+		Ui_MakePrimUI* ui;
+
+	public slots:
+		void hiResToggled (bool on);
+};
+
+void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines);
+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.
+LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num);
+
+QString radialFileName (PrimitiveType type, int segs, int divs, int num);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/radioGroup.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,194 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// I still find the radio group useful... find a way to use this in Designer.
+// I probably need to look into how to make Designer plugins.
+// TODO: try make this usable in Designer
+
+#include <QBoxLayout>
+#include <QRadioButton>
+#include <QButtonGroup>
+#include <QCheckBox>
+#include <map>
+
+#include "radioGroup.h"
+
+// =============================================================================
+//
+RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent)
+{
+	init (Qt::Vertical);
+}
+
+// =============================================================================
+//
+QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false)
+{
+	return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom;
+}
+
+// =============================================================================
+//
+bool RadioGroup::isChecked (int n) const
+{
+	return m_buttonGroup->checkedId() == n;
+}
+
+// =============================================================================
+//
+void RadioGroup::init (Qt::Orientation orient)
+{
+	m_vert = orient == Qt::Vertical;
+
+	m_buttonGroup = new QButtonGroup;
+	m_oldId = m_curId = 0;
+	m_coreLayout = null;
+
+	m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom);
+	setLayout (m_coreLayout);
+
+	// Init the first row with a break
+	rowBreak();
+
+	connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int)));
+	connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int)));
+}
+
+// =============================================================================
+//
+RadioGroup::RadioGroup (const QString& title, QList<char const*> entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) :
+		QGroupBox (title, parent),
+		m_defId (defaultId)
+{
+	init (orient);
+	m_oldId = m_defId;
+
+	for (const char* entry : entries)
+		addButton (entry);
+}
+
+// =============================================================================
+//
+void RadioGroup::rowBreak()
+{
+	QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight);
+	m_currentLayout = newLayout;
+	m_layouts << newLayout;
+
+	m_coreLayout->addLayout (newLayout);
+}
+
+// =============================================================================
+//
+void RadioGroup::addButton (const char* entry)
+{
+	QRadioButton* button = new QRadioButton (entry);
+	addButton (button);
+}
+
+// =============================================================================
+//
+void RadioGroup::addButton (QRadioButton* button)
+{
+	bool const selectThis = (m_curId == m_defId);
+
+	m_objects << button;
+	m_buttonGroup->addButton (button, m_curId++);
+	m_currentLayout->addWidget (button);
+
+	if (selectThis)
+		button->setChecked (true);
+}
+
+// =============================================================================
+//
+RadioGroup& RadioGroup::operator<< (QRadioButton* button)
+{
+	addButton (button);
+	return *this;
+}
+
+// =============================================================================
+//
+RadioGroup& RadioGroup::operator<< (const char* entry)
+{
+	addButton (entry);
+	return *this;
+}
+
+// =============================================================================
+//
+void RadioGroup::setCurrentRow (int row)
+{
+	m_currentLayout = m_layouts[row];
+}
+
+// =============================================================================
+//
+int RadioGroup::value() const
+{
+	return m_buttonGroup->checkedId();
+}
+
+// =============================================================================
+//
+void RadioGroup::setValue (int val)
+{
+	m_buttonGroup->button (val)->setChecked (true);
+}
+
+// =============================================================================
+//
+QRadioButton* RadioGroup::operator[] (int n) const
+{
+	return m_objects[n];
+}
+
+// =============================================================================
+//
+void RadioGroup::slot_buttonPressed (int btn)
+{
+	emit buttonPressed (btn);
+
+	m_oldId = m_buttonGroup->checkedId();
+}
+
+// =============================================================================
+//
+void RadioGroup::slot_buttonReleased (int btn)
+{
+	emit buttonReleased (btn);
+	int newid = m_buttonGroup->checkedId();
+
+	if (m_oldId != newid)
+		emit valueChanged (newid);
+}
+
+// =============================================================================
+//
+RadioGroup::Iterator RadioGroup::begin()
+{
+	return m_objects.begin();
+}
+
+// =============================================================================
+//
+RadioGroup::Iterator RadioGroup::end()
+{
+	return m_objects.end();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/radioGroup.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,92 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QGroupBox>
+#include <QSpinBox>
+#include <map>
+#include "main.h"
+#include "basics.h"
+
+class QIcon;
+class QCheckBox;
+class QButtonGroup;
+class QBoxLayout;
+class QRadioButton;
+
+// =============================================================================
+// RadioGroup
+//
+// Convenience widget - is a groupbox of radio buttons.
+// =============================================================================
+class RadioGroup : public QGroupBox
+{
+	Q_OBJECT
+
+	public:
+		typedef QList<QRadioButton*>::Iterator Iterator;
+
+		explicit RadioGroup()
+		{
+			init (Qt::Vertical);
+		}
+
+		explicit RadioGroup (QWidget* parent = null) : QGroupBox (parent)
+		{
+			init (Qt::Vertical);
+		}
+
+		explicit RadioGroup (const QString& title, QWidget* parent = null);
+		explicit RadioGroup (const QString& title, QList<char const*> entries, int const defaultId,
+			const Qt::Orientation orient = Qt::Vertical, QWidget* parent = null);
+
+		void            addButton	(const char* entry);
+		void            addButton	(QRadioButton* button);
+		Iterator        begin();
+		Iterator        end();
+		void            init (Qt::Orientation orient);
+		bool            isChecked (int n) const;
+		void            rowBreak();
+		void            setCurrentRow (int row);
+		void            setValue (int val);
+		int             value() const;
+
+		QRadioButton*   operator[] (int n) const;
+		RadioGroup&     operator<< (QRadioButton* button);
+		RadioGroup&     operator<< (const char* entry);
+
+	signals:
+		void buttonPressed (int btn);
+		void buttonReleased (int btn);
+		void valueChanged (int val);
+
+	private:
+		QList<QRadioButton*> m_objects;
+		QList<QBoxLayout*> m_layouts;
+		QBoxLayout* m_coreLayout;
+		QBoxLayout* m_currentLayout;
+		bool m_vert;
+		int m_curId, m_defId, m_oldId;
+		QButtonGroup* m_buttonGroup;
+
+		Q_DISABLE_COPY (RadioGroup)
+
+	private slots:
+		void slot_buttonPressed (int btn);
+		void slot_buttonReleased (int btn);
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/version.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,57 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "version.h"
+#include "gitinfo.h"
+
+char gVersionString[64] = {'\0'};
+char gFullVersionString[256] = {'\0'};
+
+// =============================================================================
+//
+const char* versionString()
+{
+	if (gVersionString[0] == '\0')
+	{
+#if VERSION_PATCH == 0
+		sprintf (gVersionString, "%d.%d", VERSION_MAJOR, VERSION_MINOR);
+#else
+		sprintf (gVersionString, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
+#endif // VERSION_PATCH
+	}
+
+	return gVersionString;
+}
+
+// =============================================================================
+//
+const char* fullVersionString()
+{
+	if (gFullVersionString[0] == '\0')
+	{
+#if BUILD_ID != BUILD_RELEASE
+		strcpy (gFullVersionString, GIT_DESCRIPTION);
+#else
+		sprintf (gFullVersionString, "v%s", versionString());
+#endif
+	}
+
+	return gFullVersionString;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/version.h	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,55 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//! \file version.h
+//! Contains macros related to application name and version.
+
+#pragma once
+
+//! The application name.
+#define APPNAME			"LDForge"
+
+//! The unix-style name of the application. used in filenames
+#define UNIXNAME		"ldforge"
+
+//! The major version number.
+#define VERSION_MAJOR	0
+
+//! The minor version number.
+#define VERSION_MINOR	3
+
+//! The patch level version number.
+#define VERSION_PATCH	0
+
+//! The build ID, use either BUILD_INTERNAL or BUILD_RELEASE
+#define BUILD_ID		BUILD_INTERNAL
+
+//! The build code for internal builds
+#define BUILD_INTERNAL	0
+
+//! The build code for release builds.
+#define BUILD_RELEASE	1
+
+// =============================================
+#ifdef DEBUG
+# undef RELEASE
+#endif // DEBUG
+
+#ifdef RELEASE
+# undef DEBUG
+#endif // RELEASE

mercurial