- changed source file extension from .cpp to .cc

Fri, 13 Dec 2013 20:01:49 +0200

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Fri, 13 Dec 2013 20:01:49 +0200
changeset 557
04e140bdeb0b
parent 556
5f4395ec5db0
child 558
5f6e30e0450c

- changed source file extension from .cpp to .cc

ldforge.pro file | annotate | diff | comparison | revisions
src/addObjectDialog.cc file | annotate | diff | comparison | revisions
src/addObjectDialog.cpp file | annotate | diff | comparison | revisions
src/colorSelectDialog.cc file | annotate | diff | comparison | revisions
src/colorSelectDialog.cpp file | annotate | diff | comparison | revisions
src/colors.cc file | annotate | diff | comparison | revisions
src/colors.cpp file | annotate | diff | comparison | revisions
src/config.cc file | annotate | diff | comparison | revisions
src/config.cpp file | annotate | diff | comparison | revisions
src/configDialog.cc file | annotate | diff | comparison | revisions
src/configDialog.cpp file | annotate | diff | comparison | revisions
src/crashcatcher.cc file | annotate | diff | comparison | revisions
src/crashcatcher.cpp file | annotate | diff | comparison | revisions
src/dialogs.cc file | annotate | diff | comparison | revisions
src/dialogs.cpp file | annotate | diff | comparison | revisions
src/docs.cc file | annotate | diff | comparison | revisions
src/docs.cpp file | annotate | diff | comparison | revisions
src/document.cc file | annotate | diff | comparison | revisions
src/document.cpp file | annotate | diff | comparison | revisions
src/download.cc file | annotate | diff | comparison | revisions
src/download.cpp file | annotate | diff | comparison | revisions
src/extprogs.cc file | annotate | diff | comparison | revisions
src/extprogs.cpp file | annotate | diff | comparison | revisions
src/gldraw.cc file | annotate | diff | comparison | revisions
src/gldraw.cpp file | annotate | diff | comparison | revisions
src/gui.cc file | annotate | diff | comparison | revisions
src/gui.cpp file | annotate | diff | comparison | revisions
src/gui_actions.cc file | annotate | diff | comparison | revisions
src/gui_actions.cpp file | annotate | diff | comparison | revisions
src/gui_editactions.cc file | annotate | diff | comparison | revisions
src/gui_editactions.cpp file | annotate | diff | comparison | revisions
src/history.cc file | annotate | diff | comparison | revisions
src/history.cpp file | annotate | diff | comparison | revisions
src/ldconfig.cc file | annotate | diff | comparison | revisions
src/ldconfig.cpp file | annotate | diff | comparison | revisions
src/ldtypes.cc file | annotate | diff | comparison | revisions
src/ldtypes.cpp file | annotate | diff | comparison | revisions
src/main.cc file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/messagelog.cc file | annotate | diff | comparison | revisions
src/messagelog.cpp file | annotate | diff | comparison | revisions
src/misc.cc file | annotate | diff | comparison | revisions
src/misc.cpp file | annotate | diff | comparison | revisions
src/primitives.cc file | annotate | diff | comparison | revisions
src/primitives.cpp file | annotate | diff | comparison | revisions
src/types.cc file | annotate | diff | comparison | revisions
src/types.cpp file | annotate | diff | comparison | revisions
src/types.h file | annotate | diff | comparison | revisions
src/widgets.cc file | annotate | diff | comparison | revisions
src/widgets.cpp file | annotate | diff | comparison | revisions
--- a/ldforge.pro	Fri Dec 13 00:39:49 2013 +0200
+++ b/ldforge.pro	Fri Dec 13 20:01:49 2013 +0200
@@ -15,7 +15,7 @@
 MOC_DIR         = ./build_shared/
 RCC_DIR         = ./build_shared/
 UI_DIR          = ./build_shared/
-SOURCES         = src/*.cpp
+SOURCES         = src/*.cc
 HEADERS         = src/*.h
 FORMS           = ui/*.ui
 QT             += opengl network
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/addObjectDialog.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,405 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QGridLayout>
+#include <QCheckBox>
+#include <QDialogButtonBox>
+#include <QSpinBox>
+#include <QLabel>
+#include <QListWidget>
+#include <QTreeWidget>
+#include <QLineEdit>
+#include <QPushButton>
+#include "gui.h"
+#include "addObjectDialog.h"
+#include "document.h"
+#include "colors.h"
+#include "colorSelectDialog.h"
+#include "history.h"
+#include "widgets.h"
+#include "misc.h"
+#include "primitives.h"
+#include "moc_addObjectDialog.cpp"
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+class SubfileListItem : public QTreeWidgetItem
+{	PROPERTY (public,	Primitive*,	PrimitiveInfo, NO_OPS,	STOCK_WRITE)
+
+	public:
+		SubfileListItem (QTreeWidgetItem* parent, Primitive* info) :
+			QTreeWidgetItem (parent),
+			m_PrimitiveInfo (info) {}
+
+		SubfileListItem (QTreeWidget* parent, Primitive* info) :
+			QTreeWidgetItem (parent),
+			m_PrimitiveInfo (info) {}
+};
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) :
+	QDialog (parent)
+{	setlocale (LC_ALL, "C");
+
+	int coordCount = 0;
+	str typeName = LDObject::typeName (type);
+
+	switch (type)
+	{	case LDObject::Comment:
+		{	le_comment = new QLineEdit;
+
+			if (obj)
+				le_comment->setText (static_cast<LDComment*> (obj)->text);
+
+			le_comment->setMinimumWidth (384);
+		} break;
+
+		case LDObject::Line:
+		{	coordCount = 6;
+		} break;
+
+		case LDObject::Triangle:
+		{	coordCount = 9;
+		} break;
+
+		case LDObject::Quad:
+		case LDObject::CondLine:
+		{	coordCount = 12;
+		} break;
+
+		case LDObject::Vertex:
+		{	coordCount = 3;
+		} break;
+
+		case LDObject::BFC:
+		{	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::statements[i]);
+			}
+
+			if (obj)
+				rb_bfcType->setValue ( (int) static_cast<LDBFC*> (obj)->type);
+		} break;
+
+		case LDObject::Subfile:
+		{	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->getName());
+				QList<QTreeWidgetItem*> subfileItems;
+
+				for (Primitive& prim : cat->prims)
+				{	SubfileListItem* item = new SubfileListItem (parentItem, &prim);
+					item->setText (0, fmt ("%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)->getFileInfo()->getName() == 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->getFileInfo()->getName());
+			}
+		} break;
+
+		default:
+		{	critical (fmt ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName));
+		} return;
+	}
+
+	QPixmap icon = getIcon (fmt ("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->getColor();
+		else
+			colnum = (type == LDObject::CondLine || type == LDObject::Line) ? 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::Line:
+		case LDObject::CondLine:
+		case LDObject::Triangle:
+		case LDObject::Quad:
+
+			// 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->getVertex (i).coord (j));
+			}
+
+			break;
+
+		case LDObject::Comment:
+			layout->addWidget (le_comment, 0, 1);
+			break;
+
+		case LDObject::BFC:
+			layout->addWidget (rb_bfcType, 0, 1);
+			break;
+
+		case LDObject::Subfile:
+			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->getPosition()[ax]);
+
+			defaultMatrix = mo->getTransform();
+		}
+
+		le_matrix->setText (defaultMatrix.stringRep());
+		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 (fmt (tr ("Edit %1"), typeName));
+
+	setWindowIcon (icon);
+	delete defaults;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void AddObjectDialog::setButtonBackground (QPushButton* button, int colnum)
+{	LDColor* col = getColor (colnum);
+
+	button->setIcon (getIcon ("palette"));
+	button->setAutoFillBackground (true);
+
+	if (col)
+		button->setStyleSheet (fmt ("background-color: %1", col->hexcode));
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str AddObjectDialog::currentSubfileName()
+{	SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem());
+
+	if (item->getPrimitiveInfo() == null)
+		return ""; // selected a heading
+
+	return item->getPrimitiveInfo()->name;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void AddObjectDialog::slot_colorButtonClicked()
+{	ColorSelector::selectColor (colnum, colnum, this);
+	setButtonBackground (pb_color, colnum);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void AddObjectDialog::slot_subfileTypeChanged()
+{	str 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->getType() == LDObject::Error)
+		return;
+
+	if (type == LDObject::Empty)
+		return; // Nothing to edit with empties
+
+	const bool newObject = (obj == null);
+	matrix transform = g_identity;
+	AddObjectDialog dlg (type, obj);
+
+	assert (!obj || obj->getType() == type);
+
+	if (dlg.exec() == false)
+		return;
+
+	if (type == LDObject::Subfile)
+	{	QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts);
+
+		if (matrixstrvals.size() == 9)
+		{	double matrixvals[9];
+			int i = 0;
+
+			for (str val : matrixstrvals)
+				matrixvals[i++] = val.toFloat();
+
+			transform = matrix (matrixvals);
+		}
+	}
+
+	switch (type)
+	{	case LDObject::Comment:
+		{	LDComment* comm = initObj<LDComment> (obj);
+			comm->text = dlg.le_comment->text();
+		}
+		break;
+
+		case LDObject::Line:
+		case LDObject::Triangle:
+		case LDObject::Quad:
+		case LDObject::CondLine:
+		{	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::BFC:
+		{	LDBFC* bfc = initObj<LDBFC> (obj);
+			bfc->type = (LDBFC::Type) dlg.rb_bfcType->value();
+		} break;
+
+		case LDObject::Vertex:
+		{	LDVertex* vert = initObj<LDVertex> (obj);
+
+			for_axes (ax)
+				vert->pos[ax] = dlg.dsb_coords[ax]->value();
+		}
+		break;
+
+		case LDObject::Subfile:
+		{	str name = dlg.le_subfileName->text();
+
+			if (name.length() == 0)
+				return; // no subfile filename
+
+			LDDocument* file = getDocument (name);
+
+			if (!file)
+			{	critical (fmt ("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->doFullRefresh();
+}
--- a/src/addObjectDialog.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,405 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QGridLayout>
-#include <QCheckBox>
-#include <QDialogButtonBox>
-#include <QSpinBox>
-#include <QLabel>
-#include <QListWidget>
-#include <QTreeWidget>
-#include <QLineEdit>
-#include <QPushButton>
-#include "gui.h"
-#include "addObjectDialog.h"
-#include "document.h"
-#include "colors.h"
-#include "colorSelectDialog.h"
-#include "history.h"
-#include "widgets.h"
-#include "misc.h"
-#include "primitives.h"
-#include "moc_addObjectDialog.cpp"
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-class SubfileListItem : public QTreeWidgetItem
-{	PROPERTY (public,	Primitive*,	PrimitiveInfo, NO_OPS,	STOCK_WRITE)
-
-	public:
-		SubfileListItem (QTreeWidgetItem* parent, Primitive* info) :
-			QTreeWidgetItem (parent),
-			m_PrimitiveInfo (info) {}
-
-		SubfileListItem (QTreeWidget* parent, Primitive* info) :
-			QTreeWidgetItem (parent),
-			m_PrimitiveInfo (info) {}
-};
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) :
-	QDialog (parent)
-{	setlocale (LC_ALL, "C");
-
-	int coordCount = 0;
-	str typeName = LDObject::typeName (type);
-
-	switch (type)
-	{	case LDObject::Comment:
-		{	le_comment = new QLineEdit;
-
-			if (obj)
-				le_comment->setText (static_cast<LDComment*> (obj)->text);
-
-			le_comment->setMinimumWidth (384);
-		} break;
-
-		case LDObject::Line:
-		{	coordCount = 6;
-		} break;
-
-		case LDObject::Triangle:
-		{	coordCount = 9;
-		} break;
-
-		case LDObject::Quad:
-		case LDObject::CondLine:
-		{	coordCount = 12;
-		} break;
-
-		case LDObject::Vertex:
-		{	coordCount = 3;
-		} break;
-
-		case LDObject::BFC:
-		{	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::statements[i]);
-			}
-
-			if (obj)
-				rb_bfcType->setValue ( (int) static_cast<LDBFC*> (obj)->type);
-		} break;
-
-		case LDObject::Subfile:
-		{	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->getName());
-				QList<QTreeWidgetItem*> subfileItems;
-
-				for (Primitive& prim : cat->prims)
-				{	SubfileListItem* item = new SubfileListItem (parentItem, &prim);
-					item->setText (0, fmt ("%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)->getFileInfo()->getName() == 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->getFileInfo()->getName());
-			}
-		} break;
-
-		default:
-		{	critical (fmt ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName));
-		} return;
-	}
-
-	QPixmap icon = getIcon (fmt ("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->getColor();
-		else
-			colnum = (type == LDObject::CondLine || type == LDObject::Line) ? 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::Line:
-		case LDObject::CondLine:
-		case LDObject::Triangle:
-		case LDObject::Quad:
-
-			// 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->getVertex (i).coord (j));
-			}
-
-			break;
-
-		case LDObject::Comment:
-			layout->addWidget (le_comment, 0, 1);
-			break;
-
-		case LDObject::BFC:
-			layout->addWidget (rb_bfcType, 0, 1);
-			break;
-
-		case LDObject::Subfile:
-			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 (const Axis ax : g_Axes)
-				dsb_coords[ax]->setValue (mo->getPosition()[ax]);
-
-			defaultMatrix = mo->getTransform();
-		}
-
-		le_matrix->setText (defaultMatrix.stringRep());
-		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 (fmt (tr ("Edit %1"), typeName));
-
-	setWindowIcon (icon);
-	delete defaults;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void AddObjectDialog::setButtonBackground (QPushButton* button, int colnum)
-{	LDColor* col = getColor (colnum);
-
-	button->setIcon (getIcon ("palette"));
-	button->setAutoFillBackground (true);
-
-	if (col)
-		button->setStyleSheet (fmt ("background-color: %1", col->hexcode));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str AddObjectDialog::currentSubfileName()
-{	SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem());
-
-	if (item->getPrimitiveInfo() == null)
-		return ""; // selected a heading
-
-	return item->getPrimitiveInfo()->name;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void AddObjectDialog::slot_colorButtonClicked()
-{	ColorSelector::selectColor (colnum, colnum, this);
-	setButtonBackground (pb_color, colnum);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void AddObjectDialog::slot_subfileTypeChanged()
-{	str 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->getType() == LDObject::Error)
-		return;
-
-	if (type == LDObject::Empty)
-		return; // Nothing to edit with empties
-
-	const bool newObject = (obj == null);
-	matrix transform = g_identity;
-	AddObjectDialog dlg (type, obj);
-
-	assert (!obj || obj->getType() == type);
-
-	if (dlg.exec() == false)
-		return;
-
-	if (type == LDObject::Subfile)
-	{	QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts);
-
-		if (matrixstrvals.size() == 9)
-		{	double matrixvals[9];
-			int i = 0;
-
-			for (str val : matrixstrvals)
-				matrixvals[i++] = val.toFloat();
-
-			transform = matrix (matrixvals);
-		}
-	}
-
-	switch (type)
-	{	case LDObject::Comment:
-		{	LDComment* comm = initObj<LDComment> (obj);
-			comm->text = dlg.le_comment->text();
-		}
-		break;
-
-		case LDObject::Line:
-		case LDObject::Triangle:
-		case LDObject::Quad:
-		case LDObject::CondLine:
-		{	if (!obj)
-				obj = LDObject::getDefault (type);
-
-			for (int i = 0; i < obj->vertices(); ++i)
-			{	vertex v;
-
-				for (const Axis ax : g_Axes)
-					v[ax] = dlg.dsb_coords[ (i * 3) + ax]->value();
-
-				obj->setVertex (i, v);
-			}
-		} break;
-
-		case LDObject::BFC:
-		{	LDBFC* bfc = initObj<LDBFC> (obj);
-			bfc->type = (LDBFC::Type) dlg.rb_bfcType->value();
-		} break;
-
-		case LDObject::Vertex:
-		{	LDVertex* vert = initObj<LDVertex> (obj);
-
-		for (const Axis ax : g_Axes)
-				vert->pos[ax] = dlg.dsb_coords[ax]->value();
-		}
-		break;
-
-		case LDObject::Subfile:
-		{	str name = dlg.le_subfileName->text();
-
-			if (name.length() == 0)
-				return; // no subfile filename
-
-			LDDocument* file = getDocument (name);
-
-			if (!file)
-			{	critical (fmt ("Couldn't open `%1': %2", name, strerror (errno)));
-				return;
-			}
-
-			LDSubfile* ref = initObj<LDSubfile> (obj);
-			assert (ref);
-
-			for (const Axis ax : g_Axes)
-				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->doFullRefresh();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colorSelectDialog.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,195 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  colorSelectDialog.cpp: Color selector box.
+ */
+
+#include <QGraphicsScene>
+#include <QGraphicsItem>
+#include <QMouseEvent>
+#include <QScrollBar>
+
+#include "main.h"
+#include "gui.h"
+#include "colorSelectDialog.h"
+#include "colors.h"
+#include "config.h"
+#include "misc.h"
+#include "ui_colorsel.h"
+#include "moc_colorSelectDialog.cpp"
+
+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 (fmt ("%1", i));
+		numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black);
+		numtext->setPos (x, y);
+
+		if (getSelection() && i == getSelection()->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 (!getSelection())
+	{	ui->colorLabel->setText ("---");
+		return;
+	}
+
+	ui->colorLabel->setText (fmt ("%1 - %2", getSelection()->index, getSelection()->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 (getSelection() && getSelection()->index >= visibleColors)
+		{	int y = (getSelection()->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.getSelection() != null)
+	{	val = dlg.getSelection()->index;
+		return true;
+	}
+
+	return false;
+}
\ No newline at end of file
--- a/src/colorSelectDialog.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,195 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  colorSelectDialog.cpp: Color selector box.
- */
-
-#include <QGraphicsScene>
-#include <QGraphicsItem>
-#include <QMouseEvent>
-#include <QScrollBar>
-
-#include "main.h"
-#include "gui.h"
-#include "colorSelectDialog.h"
-#include "colors.h"
-#include "config.h"
-#include "misc.h"
-#include "ui_colorsel.h"
-#include "moc_colorSelectDialog.cpp"
-
-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 (fmt ("%1", i));
-		numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black);
-		numtext->setPos (x, y);
-
-		if (getSelection() && i == getSelection()->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 (!getSelection())
-	{	ui->colorLabel->setText ("---");
-		return;
-	}
-
-	ui->colorLabel->setText (fmt ("%1 - %2", getSelection()->index, getSelection()->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 (getSelection() && getSelection()->index >= visibleColors)
-		{	int y = (getSelection()->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.getSelection() != null)
-	{	val = dlg.getSelection()->index;
-		return true;
-	}
-
-	return false;
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colors.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,78 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  colors.cpp: 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 "gui.h"
+#include "ldconfig.h"
+#include <QColor>
+
+static LDColor* g_LDColors[MAX_COLORS];
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void initColors()
+{	LDColor* col;
+	log ("%1: initializing color information.\n", __func__);
+
+	// 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.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  colors.cpp: 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 "gui.h"
-#include "ldconfig.h"
-#include <QColor>
-
-static LDColor* g_LDColors[MAX_COLORS];
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void initColors()
-{	LDColor* col;
-	log ("%1: initializing color information.\n", __func__);
-
-	// 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/config.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,140 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  config.cpp: 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 "config.h"
+#include "misc.h"
+#include "gui.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;
+
+// =============================================================================
+// Get the QSettings object. A portable build refers to a file in the current
+// directory, a non-portable build to ~/.config/LDForge or to registry.
+// -----------------------------------------------------------------------------
+static QSettings* getSettingsObject()
+{
+#ifdef PORTABLE
+	return new QSettings (str (APPNAME).toLower() + EXTENSION, QSettings::IniFormat);
+#else
+	return new QSettings;
+#endif // PORTABLE
+}
+
+Config::Config (const char* name, const char* defstring) :
+	name (name), m_defstring (defstring) {}
+
+// =============================================================================
+// Load the configuration from file
+// -----------------------------------------------------------------------------
+bool Config::load()
+{	QSettings* settings = getSettingsObject();
+	log ("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->defaultVariant());
+		cfg->loadFromVariant (val);
+	}
+
+	settings->deleteLater();
+	return true;
+}
+
+// =============================================================================
+// Save the configuration to disk
+// -----------------------------------------------------------------------------
+bool Config::save()
+{	QSettings* settings = getSettingsObject();
+	log ("Saving configuration to %1...\n", settings->fileName());
+
+	for (Config* cfg : g_configPointers)
+	{	if (!cfg)
+			break;
+
+		if (cfg->isDefault())
+			continue;
+
+		settings->setValue (cfg->name, cfg->toVariant());
+	}
+
+	settings->sync();
+	settings->deleteLater();
+	return true;
+}
+
+// =============================================================================
+// Reset configuration defaults.
+// -----------------------------------------------------------------------------
+void Config::reset()
+{	for (Config * cfg : g_configPointers)
+	{	if (!cfg)
+			break;
+
+		cfg->resetValue();
+	}
+}
+
+// =============================================================================
+// Where is the configuration file located at? Note that the Windows build uses
+// the registry so only use this with PORTABLE code.
+// -----------------------------------------------------------------------------
+str Config::filepath (str file)
+{	return Config::dirpath() + DIRSLASH + file;
+}
+
+// =============================================================================
+// Directory of the configuration file. PORTABLE code here as well.
+// -----------------------------------------------------------------------------
+str 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;
+}
--- a/src/config.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  config.cpp: 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 "config.h"
-#include "misc.h"
-#include "gui.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;
-
-// =============================================================================
-// Get the QSettings object. A portable build refers to a file in the current
-// directory, a non-portable build to ~/.config/LDForge or to registry.
-// -----------------------------------------------------------------------------
-static QSettings* getSettingsObject()
-{
-#ifdef PORTABLE
-	return new QSettings (str (APPNAME).toLower() + EXTENSION, QSettings::IniFormat);
-#else
-	return new QSettings;
-#endif // PORTABLE
-}
-
-Config::Config (const char* name, const char* defstring) :
-	name (name), m_defstring (defstring) {}
-
-// =============================================================================
-// Load the configuration from file
-// -----------------------------------------------------------------------------
-bool Config::load()
-{	QSettings* settings = getSettingsObject();
-	log ("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->defaultVariant());
-		cfg->loadFromVariant (val);
-	}
-
-	settings->deleteLater();
-	return true;
-}
-
-// =============================================================================
-// Save the configuration to disk
-// -----------------------------------------------------------------------------
-bool Config::save()
-{	QSettings* settings = getSettingsObject();
-	log ("Saving configuration to %1...\n", settings->fileName());
-
-	for (Config* cfg : g_configPointers)
-	{	if (!cfg)
-			break;
-
-		if (cfg->isDefault())
-			continue;
-
-		settings->setValue (cfg->name, cfg->toVariant());
-	}
-
-	settings->sync();
-	settings->deleteLater();
-	return true;
-}
-
-// =============================================================================
-// Reset configuration defaults.
-// -----------------------------------------------------------------------------
-void Config::reset()
-{	for (Config * cfg : g_configPointers)
-	{	if (!cfg)
-			break;
-
-		cfg->resetValue();
-	}
-}
-
-// =============================================================================
-// Where is the configuration file located at? Note that the Windows build uses
-// the registry so only use this with PORTABLE code.
-// -----------------------------------------------------------------------------
-str Config::filepath (str file)
-{	return Config::dirpath() + DIRSLASH + file;
-}
-
-// =============================================================================
-// Directory of the configuration file. PORTABLE code here as well.
-// -----------------------------------------------------------------------------
-str 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;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/configDialog.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,719 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  configDialog.cpp: Settings dialog and everything related to it.
+ *  Actual configuration core is in config.cpp.
+ */
+
+#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 "document.h"
+#include "config.h"
+#include "misc.h"
+#include "colors.h"
+#include "colorSelectDialog.h"
+#include "gldraw.h"
+#include "ui_config.h"
+#include "moc_configDialog.cpp"
+
+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 (String, ld_defaultname);
+extern_cfg (String, ld_defaultuser);
+extern_cfg (Int, ld_defaultlicense);
+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);
+
+#define act(N) extern_cfg (KeySequence, key_##N);
+#include "actions.h"
+
+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()));
+
+	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);
+
+	int i = 0;
+#define act(N) addShortcut (key_##N, ACTION(N), i);
+#include "actions.h"
+
+	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 : initlist<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 (fmt ("grid-%1", str (g_GridInfo[i].name).toLower())));
+
+		// Text label
+		lb_gridLabels[i] = new QLabel (fmt ("%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;
+			dsb_gridData[i][j]->setValue (g_GridInfo[i].confs[j]->value);
+			gridlayout->addWidget (dsb_gridData[i][j], i + 1, j + 1);
+		}
+	}
+
+	ui->grids->setLayout (gridlayout);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static const struct LDExtProgInfo
+{	const str name, iconname;
+	StringConfig* const path;
+	mutable QLineEdit* input;
+	mutable QPushButton* setPathButton;
+#ifndef _WIN32
+	BoolConfig* const wine;
+	mutable 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 (const 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->value);
+		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();
+	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]->value = dsb_gridData[i][j]->value();
+
+	// Apply key shortcuts
+#define act(N) ACTION(N)->setShortcut (key_##N);
+#include "actions.h"
+
+	// 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->updateToolBars();
+	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.getColor();
+
+			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->getColor()->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 (StringConfig& conf, QPushButton* button)
+{	QColor col = QColorDialog::getColor (QColor (conf));
+
+	if (col.isValid())
+	{	uchar r = col.red(),
+				  g = col.green(),
+				  b = col.blue();
+		conf.value.sprintf ("#%.2X%.2X%.2X", r, g, b);
+		setButtonBackground (button, conf.value);
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ConfigDialog::slot_setGLBackground()
+{	pickColor (gl_bgcolor, ui->backgroundColorButton);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ConfigDialog::slot_setGLForeground()
+{	pickColor (gl_maincolor, ui->mainColorButton);
+}
+
+// =============================================================================
+// Sets background color of a given button.
+// -----------------------------------------------------------------------------
+void ConfigDialog::setButtonBackground (QPushButton* button, str value)
+{	button->setIcon (getIcon ("colorselect"));
+	button->setAutoFillBackground (true);
+	button->setStyleSheet (fmt ("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->getKeyConfig(), this))
+		setShortcutText (item);
+}
+
+// =============================================================================
+// Reset a shortcut to defaults
+// -----------------------------------------------------------------------------
+void ConfigDialog::slot_resetShortcut()
+{	QList<ShortcutListItem*> sel = getShortcutSelection();
+
+	for (ShortcutListItem* item : sel)
+	{	item->getKeyConfig()->reset();
+		setShortcutText (item);
+	}
+}
+
+// =============================================================================
+// Remove the shortcut of an action.
+// -----------------------------------------------------------------------------
+void ConfigDialog::slot_clearShortcut()
+{	QList<ShortcutListItem*> sel = getShortcutSelection();
+
+	for (ShortcutListItem* item : sel)
+	{	item->getKeyConfig()->value = 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);
+	str fpath = QFileDialog::getOpenFileName (this, fmt ("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()
+{	str 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->getAction();
+	str label = act->iconText();
+	str keybind = item->getKeyConfig()->value.toString();
+	item->setText (fmt ("%1 (%2)", label, keybind));
+}
+
+// =============================================================================
+// Gets the configuration string of the quick color toolbar
+// -----------------------------------------------------------------------------
+str ConfigDialog::quickColorString()
+{	str val;
+
+	for (const LDQuickColor& entry : quickColors)
+	{	if (val.length() > 0)
+			val += ':';
+
+		if (entry.isSeparator())
+			val += '|';
+		else
+			val += fmt ("%1", entry.getColor()->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->value, parent);
+
+	if (dlg.exec() == false)
+		return false;
+
+	cfg->value = dlg.seq;
+	return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void KeySequenceDialog::updateOutput()
+{	str shortcut = seq.toString();
+
+	if (seq == QKeySequence())
+		shortcut = "&lt;empty&gt;";
+
+	str text = fmt ("<center><b>%1</b></center>", shortcut);
+	lb_output->setText (text);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void KeySequenceDialog::keyPressEvent (QKeyEvent* ev)
+{	seq = ev->key() + ev->modifiers();
+	updateOutput();
+}
--- a/src/configDialog.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,719 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  configDialog.cpp: Settings dialog and everything related to it.
- *  Actual configuration core is in config.cpp.
- */
-
-#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 "document.h"
-#include "config.h"
-#include "misc.h"
-#include "colors.h"
-#include "colorSelectDialog.h"
-#include "gldraw.h"
-#include "ui_config.h"
-#include "moc_configDialog.cpp"
-
-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 (String, ld_defaultname);
-extern_cfg (String, ld_defaultuser);
-extern_cfg (Int, ld_defaultlicense);
-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);
-
-#define act(N) extern_cfg (KeySequence, key_##N);
-#include "actions.h"
-
-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()));
-
-	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);
-
-	int i = 0;
-#define act(N) addShortcut (key_##N, ACTION(N), i);
-#include "actions.h"
-
-	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 : initlist<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 (fmt ("grid-%1", str (g_GridInfo[i].name).toLower())));
-
-		// Text label
-		lb_gridLabels[i] = new QLabel (fmt ("%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;
-			dsb_gridData[i][j]->setValue (g_GridInfo[i].confs[j]->value);
-			gridlayout->addWidget (dsb_gridData[i][j], i + 1, j + 1);
-		}
-	}
-
-	ui->grids->setLayout (gridlayout);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static const struct LDExtProgInfo
-{	const str name, iconname;
-	StringConfig* const path;
-	mutable QLineEdit* input;
-	mutable QPushButton* setPathButton;
-#ifndef _WIN32
-	BoolConfig* const wine;
-	mutable 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 (const 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->value);
-		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();
-	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]->value = dsb_gridData[i][j]->value();
-
-	// Apply key shortcuts
-#define act(N) ACTION(N)->setShortcut (key_##N);
-#include "actions.h"
-
-	// 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->updateToolBars();
-	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.getColor();
-
-			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->getColor()->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 (StringConfig& conf, QPushButton* button)
-{	QColor col = QColorDialog::getColor (QColor (conf));
-
-	if (col.isValid())
-	{	uchar r = col.red(),
-				  g = col.green(),
-				  b = col.blue();
-		conf.value.sprintf ("#%.2X%.2X%.2X", r, g, b);
-		setButtonBackground (button, conf.value);
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ConfigDialog::slot_setGLBackground()
-{	pickColor (gl_bgcolor, ui->backgroundColorButton);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ConfigDialog::slot_setGLForeground()
-{	pickColor (gl_maincolor, ui->mainColorButton);
-}
-
-// =============================================================================
-// Sets background color of a given button.
-// -----------------------------------------------------------------------------
-void ConfigDialog::setButtonBackground (QPushButton* button, str value)
-{	button->setIcon (getIcon ("colorselect"));
-	button->setAutoFillBackground (true);
-	button->setStyleSheet (fmt ("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->getKeyConfig(), this))
-		setShortcutText (item);
-}
-
-// =============================================================================
-// Reset a shortcut to defaults
-// -----------------------------------------------------------------------------
-void ConfigDialog::slot_resetShortcut()
-{	QList<ShortcutListItem*> sel = getShortcutSelection();
-
-	for (ShortcutListItem* item : sel)
-	{	item->getKeyConfig()->reset();
-		setShortcutText (item);
-	}
-}
-
-// =============================================================================
-// Remove the shortcut of an action.
-// -----------------------------------------------------------------------------
-void ConfigDialog::slot_clearShortcut()
-{	QList<ShortcutListItem*> sel = getShortcutSelection();
-
-	for (ShortcutListItem* item : sel)
-	{	item->getKeyConfig()->value = 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);
-	str fpath = QFileDialog::getOpenFileName (this, fmt ("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()
-{	str 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->getAction();
-	str label = act->iconText();
-	str keybind = item->getKeyConfig()->value.toString();
-	item->setText (fmt ("%1 (%2)", label, keybind));
-}
-
-// =============================================================================
-// Gets the configuration string of the quick color toolbar
-// -----------------------------------------------------------------------------
-str ConfigDialog::quickColorString()
-{	str val;
-
-	for (const LDQuickColor& entry : quickColors)
-	{	if (val.length() > 0)
-			val += ':';
-
-		if (entry.isSeparator())
-			val += '|';
-		else
-			val += fmt ("%1", entry.getColor()->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->value, parent);
-
-	if (dlg.exec() == false)
-		return false;
-
-	cfg->value = dlg.seq;
-	return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void KeySequenceDialog::updateOutput()
-{	str shortcut = seq.toString();
-
-	if (seq == QKeySequence())
-		shortcut = "&lt;empty&gt;";
-
-	str text = fmt ("<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/crashcatcher.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,109 @@
+#ifdef __unix__
+
+#include <QString>
+#include <QProcess>
+#include <QTemporaryFile>
+#include <QMessageBox>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/prctl.h>
+#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 str 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 (fmt ("attach %1\n", pid).toLocal8Bit());
+		commandsFile.write (str ("backtrace full\n").toLocal8Bit());
+		commandsFile.write (str ("detach\n").toLocal8Bit());
+		commandsFile.write (str ("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.
+	prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0);
+
+	proc.waitForFinished (1000);
+	str output = QString (proc.readAllStandardOutput());
+	str err = QString (proc.readAllStandardError());
+
+	bombBox (fmt ("<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);
+
+	log ("%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)
+{	str errmsg = fmt (
+		"<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.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-#ifdef __unix__
-
-#include <QString>
-#include <QProcess>
-#include <QTemporaryFile>
-#include <QMessageBox>
-#include <unistd.h>
-#include <signal.h>
-#include <sys/prctl.h>
-#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 str 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 (fmt ("attach %1\n", pid).toLocal8Bit());
-		commandsFile.write (str ("backtrace full\n").toLocal8Bit());
-		commandsFile.write (str ("detach\n").toLocal8Bit());
-		commandsFile.write (str ("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.
-	prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0);
-
-	proc.waitForFinished (1000);
-	str output = QString (proc.readAllStandardOutput());
-	str err = QString (proc.readAllStandardError());
-
-	bombBox (fmt ("<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);
-
-	log ("%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)
-{	str errmsg = fmt (
-		"<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/dialogs.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,340 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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 "gui.h"
+#include "gldraw.h"
+#include "docs.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"
+#include "moc_dialogs.cpp"
+
+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);
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str 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 (str path)
+{	ui->path->setText (path);
+}
+
+str LDrawPathDialog::filename() const
+{	return ui->path->text();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDrawPathDialog::slot_findPath()
+{	str 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 (fmt ("<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, getNumLines());
+	updateValues();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void OpenProgressDialog::updateValues()
+{	ui->progressText->setText (fmt ("Parsing... %1 / %2", getProgress(), getNumLines()));
+	ui->progressBar->setValue (getProgress());
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void OpenProgressDialog::updateProgress (int progress)
+{	setProgress (progress);
+	updateValues();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+ExtProgPathPrompt::ExtProgPathPrompt (str progName, QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f),
+	ui (new Ui_ExtProgPath)
+{
+	ui->setupUi (this);
+	str 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()
+{	str path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter);
+
+	if (!path.isEmpty())
+		ui->m_path->setText (path);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str 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 (fmt (tr ("LDForge %1"), fullVersionString()));
+
+	QPushButton* mailButton = new QPushButton;
+	mailButton->setText ("Contact");
+	mailButton->setIcon (getIcon ("mail"));
+	ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole);
+	connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail()));
+
+	setWindowTitle ("About " APPNAME);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void AboutDialog::slot_mail()
+{	QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <slatenails64@gmail.com>?subject=LDForge"));
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void bombBox (const str& 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.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,340 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#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 "gui.h"
-#include "gldraw.h"
-#include "docs.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"
-#include "moc_dialogs.cpp"
-
-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);
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str 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 (str path)
-{	ui->path->setText (path);
-}
-
-str LDrawPathDialog::filename() const
-{	return ui->path->text();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDrawPathDialog::slot_findPath()
-{	str 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 (fmt ("<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, getNumLines());
-	updateValues();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void OpenProgressDialog::updateValues()
-{	ui->progressText->setText (fmt ("Parsing... %1 / %2", getProgress(), getNumLines()));
-	ui->progressBar->setValue (getProgress());
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void OpenProgressDialog::updateProgress (int progress)
-{	setProgress (progress);
-	updateValues();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-ExtProgPathPrompt::ExtProgPathPrompt (str progName, QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f),
-	ui (new Ui_ExtProgPath)
-{
-	ui->setupUi (this);
-	str 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()
-{	str path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter);
-
-	if (!path.isEmpty())
-		ui->m_path->setText (path);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str 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 (fmt (tr ("LDForge %1"), fullVersionString()));
-
-	QPushButton* mailButton = new QPushButton;
-	mailButton->setText ("Contact");
-	mailButton->setIcon (getIcon ("mail"));
-	ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole);
-	connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail()));
-
-	setWindowTitle ("About " APPNAME);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void AboutDialog::slot_mail()
-{	QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <slatenails64@gmail.com>?subject=LDForge"));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void bombBox (const str& 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/docs.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,72 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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/docs.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#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();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/document.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,1180 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  file.cpp: File I/O and management.
+ *  - File loading, parsing, manipulation, saving, closing.
+ *  - LDraw path verification.
+ */
+
+#include <QMessageBox>
+#include <QFileDialog>
+#include <QDir>
+#include <QApplication>
+#include "main.h"
+#include "config.h"
+#include "document.h"
+#include "misc.h"
+#include "gui.h"
+#include "history.h"
+#include "dialogs.h"
+#include "gldraw.h"
+#include "moc_document.cpp"
+
+cfg (String, io_ldpath, "");
+cfg (List, io_recentfiles, {});
+extern_cfg (String, net_downloadpath);
+extern_cfg (Bool, gl_logostuds);
+
+static bool g_loadingMainFile = false;
+static const int g_MaxRecentFiles = 5;
+static bool g_aborted = false;
+static LDDocument* g_logoedStud = null;
+static LDDocument* g_logoedStud2 = null;
+
+LDDocument* LDDocument::m_curdoc = null;
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+namespace LDPaths
+{	static str pathError;
+
+	struct
+	{	str LDConfigPath;
+		str partsPath, primsPath;
+	} pathInfo;
+
+	void initPaths()
+	{	if (!tryConfigure (io_ldpath))
+		{	LDrawPathDialog dlg (false);
+
+			if (!dlg.exec())
+				exit (0);
+
+			io_ldpath = dlg.filename();
+		}
+	}
+
+	bool tryConfigure (str path)
+	{	QDir dir;
+
+		if (!dir.cd (path))
+		{	pathError = "Directory does not exist.";
+			return false;
+		}
+
+		QStringList mustHave = { "LDConfig.ldr", "parts", "p" };
+		QStringList contents = dir.entryList (mustHave);
+
+		if (contents.size() != mustHave.size())
+		{	pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/.";
+			return false;
+		}
+
+		pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path);
+		pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path);
+		pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path);
+
+		return true;
+	}
+
+	// Accessors
+	str getError()
+	{	return pathError;
+	}
+
+	str ldconfig()
+	{	return pathInfo.LDConfigPath;
+	}
+
+	str prims()
+	{	return pathInfo.primsPath;
+	}
+
+	str parts()
+	{	return pathInfo.partsPath;
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDDocument::LDDocument()
+{	setImplicit (true);
+	setSavePosition (-1);
+	setListItem (null);
+	setHistory (new History);
+	m_History->setFile (this);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDDocument::~LDDocument()
+{	// Clear everything from the model
+	for (LDObject* obj : getObjects())
+		delete obj;
+
+	// Clear the cache as well
+	for (LDObject* obj : getCache())
+		delete obj;
+
+	delete m_History;
+
+	// Remove this file from the list of files
+	g_loadedFiles.removeOne (this);
+
+	// If we just closed the current file, we need to set the current
+	// file as something else.
+	if (this == getCurrentDocument())
+	{	bool found = false;
+
+		// Try find an explicitly loaded file - if we can't find one,
+		// we need to create a new file to switch to.
+		for (LDDocument* file : g_loadedFiles)
+		{	if (!file->isImplicit())
+			{	LDDocument::setCurrent (file);
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+			newFile();
+	}
+
+	g_win->updateDocumentList();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDDocument* findDocument (str name)
+{	for (LDDocument * file : g_loadedFiles)
+		if (!file->getName().isEmpty() && file->getShortName() == name)
+			return file;
+
+	return null;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str dirname (str path)
+{	long lastpos = path.lastIndexOf (DIRSLASH);
+
+	if (lastpos > 0)
+		return path.left (lastpos);
+
+#ifndef _WIN32
+
+	if (path[0] == DIRSLASH_CHAR)
+		return DIRSLASH;
+
+#endif // _WIN32
+
+	return "";
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str basename (str path)
+{	long lastpos = path.lastIndexOf (DIRSLASH);
+
+	if (lastpos != -1)
+		return path.mid (lastpos + 1);
+
+	return path;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+File* openLDrawFile (str relpath, bool subdirs)
+{	log ("Opening %1...\n", relpath);
+	File* f = new File;
+	str fullPath;
+
+	// LDraw models use Windows-style path separators. If we're not on Windows,
+	// replace the path separator now before opening any files. Qt expects
+	// forward-slashes as directory separators.
+#ifndef WIN32
+	relpath.replace ("\\", "/");
+#endif // WIN32
+
+	if (getCurrentDocument())
+	{	// First, try find the file in the current model's file path. We want a file
+		// in the immediate vicinity of the current model to override stock LDraw stuff.
+		str partpath = fmt ("%1" DIRSLASH "%2", dirname (getCurrentDocument()->getName()), relpath);
+
+		if (f->open (partpath, File::Read))
+			return f;
+	}
+
+	if (f->open (relpath, File::Read))
+		return f;
+
+	// Try with just the LDraw path first
+	fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath);
+
+	if (f->open (fullPath, File::Read))
+		return f;
+
+	if (subdirs)
+	{	// Look in sub-directories: parts and p. Also look in net_downloadpath, since that's
+		// where we download parts from the PT to.
+		for (const str& topdir : initlist<str> ({ io_ldpath, net_downloadpath }))
+		{	for (const str& subdir : initlist<str> ({ "parts", "p" }))
+			{	fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath);
+
+				if (f->open (fullPath, File::Read))
+					return f;
+			}
+		}
+	}
+
+	// Did not find the file.
+	log ("Could not find %1.\n", relpath);
+	delete f;
+	return null;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDFileLoader::start()
+{	setDone (false);
+	setProgress (0);
+	setAborted (false);
+
+	if (isOnForeground())
+	{	g_aborted = false;
+
+		// Show a progress dialog if we're loading the main document.here so we can
+		// show progress updates and keep the WM posted that we're still here.
+		// Of course we cannot exec() the dialog because then the dialog would
+		// block.
+		dlg = new OpenProgressDialog (g_win);
+		dlg->setNumLines (getLines().size());
+		dlg->setModal (true);
+		dlg->show();
+
+		// Connect the loader in so we can show updates
+		connect (this, SIGNAL (workDone()), dlg, SLOT (accept()));
+		connect (dlg, SIGNAL (rejected()), this, SLOT (abort()));
+	}
+	else
+		dlg = null;
+
+	// Begin working
+	work (0);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDFileLoader::work (int i)
+{	// User wishes to abort, so stop here now.
+	if (isAborted())
+	{	for (LDObject* obj : m_Objects)
+			delete obj;
+
+		m_Objects.clear();
+		setDone (true);
+		return;
+	}
+
+	// Parse up to 300 lines per iteration
+	int max = i + 300;
+
+	for (; i < max && i < (int) getLines().size(); ++i)
+	{	str line = getLines()[i];
+
+		// Trim the trailing newline
+		QChar c;
+
+		while (!line.isEmpty() && ((c = line[line.length() - 1]) == '\n' || c == '\r'))
+			line.chop (1);
+
+		LDObject* obj = parseLine (line);
+
+		// Check for parse errors and warn about tthem
+		if (obj->getType() == LDObject::Error)
+		{	log ("Couldn't parse line #%1: %2", getProgress() + 1, static_cast<LDError*> (obj)->reason);
+
+			if (getWarnings() != null)
+				(*getWarnings())++;
+		}
+
+		m_Objects << obj;
+		setProgress (i);
+
+		// If we have a dialog pointer, update the progress now
+		if (isOnForeground())
+			dlg->updateProgress (i);
+	}
+
+	// If we're done now, tell the environment we're done and stop.
+	if (i >= ((int) getLines().size()) - 1)
+	{	emit workDone();
+		setDone (true);
+		return;
+	}
+
+	// Otherwise, continue, by recursing back.
+	if (!isDone())
+	{	// If we have a dialog to show progress output to, we cannot just call
+		// work() again immediately as the dialog needs some processor cycles as
+		// well. Thus, take a detour through the event loop by using the
+		// meta-object system.
+		//
+		// This terminates the loop here and control goes back to the function
+		// which called the file loader. It will keep processing the event loop
+		// until we're ready (see loadFileContents), thus the event loop will
+		// eventually catch the invokation we throw here and send us back. Though
+		// it's not technically recursion anymore, more like a for loop. :P
+		if (isOnForeground())
+			QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i));
+		else
+			work (i + 1);
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDFileLoader::abort()
+{	setAborted (true);
+
+	if (isOnForeground())
+		g_aborted = true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok)
+{	QList<str> lines;
+	QList<LDObject*> objs;
+
+	if (numWarnings)
+		*numWarnings = 0;
+
+	// Read in the lines
+	for (str line : *f)
+		lines << line;
+
+	LDFileLoader* loader = new LDFileLoader;
+	loader->setWarnings (numWarnings);
+	loader->setLines (lines);
+	loader->setOnForeground (g_loadingMainFile);
+	loader->start();
+
+	// After start() returns, if the loader isn't done yet, it's delaying
+	// its next iteration through the event loop. We need to catch this here
+	// by telling the event loop to tick, which will tick the file loader again.
+	// We keep doing this until the file loader is ready.
+	while (loader->isDone() == false)
+		qApp->processEvents();
+
+	// If we wanted the success value, supply that now
+	if (ok)
+		*ok = !loader->isAborted();
+
+	objs = loader->getObjects();
+	return objs;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDDocument* openDocument (str path, bool search)
+{	// Convert the file name to lowercase since some parts contain uppercase
+	// file names. I'll assume here that the library will always use lowercase
+	// file names for the actual parts..
+	File* f;
+
+	if (search)
+		f = openLDrawFile (path.toLower(), true);
+	else
+	{	f = new File (path, File::Read);
+
+		if (!*f)
+		{	delete f;
+			return null;
+		}
+	}
+
+	if (!f)
+		return null;
+
+	LDDocument* load = new LDDocument;
+	load->setName (path);
+
+	int numWarnings;
+	bool ok;
+	QList<LDObject*> objs = loadFileContents (f, &numWarnings, &ok);
+
+	if (!ok)
+		return null;
+
+	for (LDObject* obj : objs)
+		load->addObject (obj);
+
+	delete f;
+	g_loadedFiles << load;
+
+	if (g_loadingMainFile)
+	{	LDDocument::setCurrent (load);
+		g_win->R()->setFile (load);
+		log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings);
+	}
+
+	return load;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool LDDocument::isSafeToClose()
+{	typedef QMessageBox msgbox;
+	setlocale (LC_ALL, "C");
+
+	// If we have unsaved changes, warn and give the option of saving.
+	if (hasUnsavedChanges())
+	{	str message = fmt ("There are unsaved changes to %1. Should it be saved?",
+						   (getName().length() > 0) ? getName() : "<anonymous>");
+
+		int button = msgbox::question (g_win, "Unsaved Changes", message,
+									   (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel);
+
+		switch (button)
+		{	case msgbox::Yes:
+
+				// If we don't have a file path yet, we have to ask the user for one.
+				if (getName().length() == 0)
+				{	str newpath = QFileDialog::getSaveFileName (g_win, "Save As",
+								  getCurrentDocument()->getName(), "LDraw files (*.dat *.ldr)");
+
+					if (newpath.length() == 0)
+						return false;
+
+					setName (newpath);
+				}
+
+				if (!save())
+				{	message = fmt (QObject::tr ("Failed to save %1: %2\nDo you still want to close?"),
+								   getName(), strerror (errno));
+
+					if (msgbox::critical (g_win, "Save Failure", message,
+										  (msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No)
+					{	return false;
+					}
+				}
+
+				break;
+
+			case msgbox::Cancel:
+				return false;
+
+			default:
+				break;
+		}
+	}
+
+	return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void closeAll()
+{	// Remove all loaded files and the objects they contain
+	QList<LDDocument*> files = g_loadedFiles;
+
+for (LDDocument * file : files)
+		delete file;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void newFile()
+{	// Create a new anonymous file and set it to our current
+	LDDocument* f = new LDDocument;
+	f->setName ("");
+	f->setImplicit (false);
+	g_loadedFiles << f;
+	LDDocument::setCurrent (f);
+	LDDocument::closeInitialFile();
+	g_win->R()->setFile (f);
+	g_win->doFullRefresh();
+	g_win->updateTitle();
+	g_win->updateActions();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void addRecentFile (str path)
+{	alias rfiles = io_recentfiles.value;
+	int idx = rfiles.indexOf (path);
+
+	// If this file already is in the list, pop it out.
+	if (idx != -1)
+	{	if (rfiles.size() == 1)
+			return; // only recent file - abort and do nothing
+
+		// Pop it out.
+		rfiles.removeAt (idx);
+	}
+
+	// If there's too many recent files, drop one out.
+	while (rfiles.size() > (g_MaxRecentFiles - 1))
+		rfiles.removeAt (0);
+
+	// Add the file
+	rfiles << path;
+
+	Config::save();
+	g_win->updateRecentFilesMenu();
+}
+
+// =============================================================================
+// Open an LDraw file and set it as the main model
+// -----------------------------------------------------------------------------
+void openMainFile (str path)
+{	g_loadingMainFile = true;
+	LDDocument* file = openDocument (path, false);
+
+	if (!file)
+	{	// Loading failed, thus drop down to a new file since we
+		// closed everything prior.
+		newFile();
+
+		if (!g_aborted)
+		{	// Tell the user loading failed.
+			setlocale (LC_ALL, "C");
+			critical (fmt (QObject::tr ("Failed to open %1: %2"), path, strerror (errno)));
+		}
+
+		g_loadingMainFile = false;
+		return;
+	}
+
+	file->setImplicit (false);
+
+	// If we have an anonymous, unchanged file open as the only open file
+	// (aside of the one we just opened), close it now.
+	LDDocument::closeInitialFile();
+
+	// Rebuild the object tree view now.
+	LDDocument::setCurrent (file);
+	g_win->doFullRefresh();
+
+	// Add it to the recent files list.
+	addRecentFile (path);
+	g_loadingMainFile = false;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool LDDocument::save (str savepath)
+{	if (!savepath.length())
+		savepath = getName();
+
+	File f (savepath, File::Write);
+
+	if (!f)
+		return false;
+
+	// If the second object in the list holds the file name, update that now.
+	// Only do this if the file is explicitly open. If it's saved into a directory
+	// called "s" or "48", prepend that into the name.
+	LDComment* fpathComment = null;
+	LDObject* first = getObject (1);
+
+	if (!isImplicit() && first != null && first->getType() == LDObject::Comment)
+	{	fpathComment = static_cast<LDComment*> (first);
+
+		if (fpathComment->text.left (6) == "Name: ")
+		{	str newname;
+			str dir = basename (dirname (savepath));
+
+			if (dir == "s" || dir == "48")
+				newname = dir + "\\";
+
+			newname += basename (savepath);
+			fpathComment->text = fmt ("Name: %1", newname);
+			g_win->buildObjList();
+		}
+	}
+
+	// File is open, now save the model to it. Note that LDraw requires files to
+	// have DOS line endings, so we terminate the lines with \r\n.
+	for (LDObject* obj : getObjects())
+		f.write (obj->raw() + "\r\n");
+
+	// File is saved, now clean up.
+	f.close();
+
+	// We have successfully saved, update the save position now.
+	setSavePosition (getHistory()->getPosition());
+	setName (savepath);
+
+	g_win->updateDocumentListItem (this);
+	g_win->updateTitle();
+	return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+#define CHECK_TOKEN_COUNT(N) \
+	if (tokens.size() != N) \
+		return new LDError (line, "Bad amount of tokens");
+
+#define CHECK_TOKEN_NUMBERS(MIN, MAX) \
+	for (int i = MIN; i <= MAX; ++i) \
+		if (!numeric (tokens[i])) \
+			return new LDError (line, fmt ("Token #%1 was `%2`, expected a number", (i + 1), tokens[i]));
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static vertex parseVertex (QStringList& s, const int n)
+{	vertex v;
+
+	for_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 (str line)
+{	QStringList tokens = line.split (" ", str::SkipEmptyParts);
+
+	if (tokens.size() <= 0)
+	{	// Line was empty, or only consisted of whitespace
+		return new LDEmpty;
+	}
+
+	if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false)
+		return new LDError (line, "Illogical line code");
+
+	int num = tokens[0][0].digitValue();
+
+	switch (num)
+	{	case 0:
+		{	// Comment
+			str comm = line.mid (line.indexOf ("0") + 1).simplified();
+
+			// Handle BFC statements
+			if (tokens.size() > 2 && tokens[1] == "BFC")
+			{	for (int i = 0; i < LDBFC::NumStatements; ++i)
+					if (comm == fmt ("BFC %1", LDBFC::statements [i]))
+						return new LDBFC ( (LDBFC::Type) i);
+
+				// MLCAD is notorious for stuffing these statements in parts it
+				// creates. The above block only handles valid statements, so we
+				// need to handle MLCAD-style invertnext, clip and noclip separately.
+				struct
+				{	str			a;
+					LDBFC::Type	b;
+				} BFCData[] =
+				{	{ "INVERTNEXT", LDBFC::InvertNext },
+					{ "NOCLIP", LDBFC::NoClip },
+					{ "CLIP", LDBFC::Clip }
+				};
+
+				for (const auto& i : BFCData)
+					if (comm == "BFC CERTIFY " + i.a)
+						return new LDBFC (i.b);
+			}
+
+			if (tokens.size() > 2 && tokens[1] == "!LDFORGE")
+			{	// Handle LDForge-specific types, they're embedded into comments too
+				if (tokens[2] == "VERTEX")
+				{	// Vertex (0 !LDFORGE VERTEX)
+					CHECK_TOKEN_COUNT (7)
+					CHECK_TOKEN_NUMBERS (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")
+				{	CHECK_TOKEN_COUNT (9);
+					CHECK_TOKEN_NUMBERS (5, 8)
+
+					LDOverlay* obj = new LDOverlay;
+					obj->setFileName (tokens[3]);
+					obj->setCamera (tokens[4].toLong());
+					obj->setX (tokens[5].toLong());
+					obj->setY (tokens[6].toLong());
+					obj->setWidth (tokens[7].toLong());
+					obj->setHeight (tokens[8].toLong());
+					return obj;
+				}
+			}
+
+			// Just a regular comment:
+			LDComment* obj = new LDComment;
+			obj->text = comm;
+			return obj;
+		}
+
+		case 1:
+		{	// Subfile
+			CHECK_TOKEN_COUNT (15)
+			CHECK_TOKEN_NUMBERS (1, 13)
+
+			// Try open the file. Disable g_loadingMainFile temporarily since we're
+			// not loading the main file now, but the subfile in question.
+			bool tmp = g_loadingMainFile;
+			g_loadingMainFile = false;
+			LDDocument* load = getDocument (tokens[14]);
+			g_loadingMainFile = tmp;
+
+			// If we cannot open the file, mark it an error
+			if (!load)
+			{	LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14]));
+				obj->setFileReferenced (tokens[14]);
+				return obj;
+			}
+
+			LDSubfile* obj = new LDSubfile;
+			obj->setColor (tokens[1].toLong());
+			obj->setPosition (parseVertex (tokens, 2));  // 2 - 4
+
+			matrix transform;
+
+			for (int i = 0; i < 9; ++i)
+				transform[i] = tokens[i + 5].toDouble(); // 5 - 13
+
+			obj->setTransform (transform);
+			obj->setFileInfo (load);
+			return obj;
+		}
+
+		case 2:
+		{	CHECK_TOKEN_COUNT (8)
+			CHECK_TOKEN_NUMBERS (1, 7)
+
+			// Line
+			LDLine* obj = new LDLine;
+			obj->setColor (tokens[1].toLong());
+
+			for (int i = 0; i < 2; ++i)
+				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 7
+
+			return obj;
+		}
+
+		case 3:
+		{	CHECK_TOKEN_COUNT (11)
+			CHECK_TOKEN_NUMBERS (1, 10)
+
+			// Triangle
+			LDTriangle* obj = new LDTriangle;
+			obj->setColor (tokens[1].toLong());
+
+			for (int i = 0; i < 3; ++i)
+				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 10
+
+			return obj;
+		}
+
+		case 4:
+		case 5:
+		{	CHECK_TOKEN_COUNT (14)
+			CHECK_TOKEN_NUMBERS (1, 13)
+
+			// Quadrilateral / Conditional line
+			LDObject* obj;
+
+			if (num == 4)
+				obj = new LDQuad;
+			else
+				obj = new LDCondLine;
+
+			obj->setColor (tokens[1].toLong());
+
+			for (int i = 0; i < 4; ++i)
+				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 13
+
+			return obj;
+		}
+
+		default: // Strange line we couldn't parse
+			return new LDError (line, "Unknown line code number");
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDDocument* getDocument (str filename)
+{	// Try find the file in the list of loaded files
+	LDDocument* doc = findDocument (filename);
+
+	// If it's not loaded, try open it
+	if (!doc)
+		doc = openDocument (filename, true);
+
+	return doc;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void reloadAllSubfiles()
+{	if (!getCurrentDocument())
+		return;
+
+	g_loadedFiles.clear();
+	g_loadedFiles << getCurrentDocument();
+
+	// Go through all objects in the current file and reload the subfiles
+	for (LDObject* obj : getCurrentDocument()->getObjects())
+	{	if (obj->getType() == LDObject::Subfile)
+		{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			LDDocument* fileInfo = getDocument (ref->getFileInfo()->getName());
+
+			if (fileInfo)
+				ref->setFileInfo (fileInfo);
+			else
+				ref->replace (new LDError (ref->raw(), "Could not open referred file"));
+		}
+
+		// Reparse gibberish files. It could be that they are invalid because
+		// of loading errors. Circumstances may be different now.
+		if (obj->getType() == LDObject::Error)
+			obj->replace (parseLine (static_cast<LDError*> (obj)->contents));
+	}
+
+	// Close all files left unused
+	LDDocument::closeUnused();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+int LDDocument::addObject (LDObject* obj)
+{	getHistory()->add (new AddHistory (getObjects().size(), obj));
+	m_Objects << obj;
+
+	if (obj->getType() == LDObject::Vertex)
+		m_Vertices << obj;
+
+	obj->setFile (this);
+	return getObjectCount() - 1;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDDocument::addObjects (const QList<LDObject*> objs)
+{	for (LDObject * obj : objs)
+		if (obj)
+			addObject (obj);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDDocument::insertObj (int pos, LDObject* obj)
+{	getHistory()->add (new AddHistory (pos, obj));
+	m_Objects.insert (pos, obj);
+	obj->setFile (this);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDDocument::forgetObject (LDObject* obj)
+{	int idx = obj->getIndex();
+	getHistory()->add (new DelHistory (idx, obj));
+	m_Objects.removeAt (idx);
+	obj->setFile (null);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool safeToCloseAll()
+{	for (LDDocument* f : g_loadedFiles)
+		if (!f->isSafeToClose())
+			return false;
+
+	return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDDocument::setObject (int idx, LDObject* obj)
+{	assert (idx < m_Objects.size());
+
+	// Mark this change to history
+	str oldcode = getObject (idx)->raw();
+	str newcode = obj->raw();
+	*m_History << new EditHistory (idx, oldcode, newcode);
+
+	obj->setFile (this);
+	m_Objects[idx] = obj;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static QList<LDDocument*> getFilesUsed (LDDocument* node)
+{	QList<LDDocument*> filesUsed;
+
+	for (LDObject* obj : node->getObjects())
+	{	if (obj->getType() != LDObject::Subfile)
+			continue;
+
+		LDSubfile* ref = static_cast<LDSubfile*> (obj);
+		filesUsed << ref->getFileInfo();
+		filesUsed << getFilesUsed (ref->getFileInfo());
+	}
+
+	return filesUsed;
+}
+
+// =============================================================================
+// Find out which files are unused and close them.
+// -----------------------------------------------------------------------------
+void LDDocument::closeUnused()
+{	QList<LDDocument*> filesUsed = getFilesUsed (getCurrentDocument());
+
+	// Anything that's explicitly opened must not be closed
+	for (LDDocument* file : g_loadedFiles)
+		if (!file->isImplicit())
+			filesUsed << file;
+
+	// Remove duplicated entries
+	removeDuplicates (filesUsed);
+
+	// Close all open files that aren't in filesUsed
+	for (LDDocument* file : g_loadedFiles)
+	{	bool isused = false;
+
+		for (LDDocument* usedFile : filesUsed)
+		{	if (file == usedFile)
+			{	isused = true;
+				break;
+			}
+		}
+
+		if (!isused)
+			delete file;
+	}
+
+	g_loadedFiles.clear();
+	g_loadedFiles << filesUsed;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDObject* LDDocument::getObject (int pos) const
+{	if (m_Objects.size() <= pos)
+		return null;
+
+	return m_Objects[pos];
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+int LDDocument::getObjectCount() const
+{	return getObjects().size();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool LDDocument::hasUnsavedChanges() const
+{	return !isImplicit() && getHistory()->getPosition() != getSavePosition();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDDocument::getShortName()
+{	if (!getName().isEmpty())
+		return basename (getName());
+
+	if (!getDefaultName().isEmpty())
+		return getDefaultName();
+
+	return tr ("<anonymous>");
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+QList<LDObject*> LDDocument::inlineContents (LDSubfile::InlineFlags flags)
+{	// Possibly substitute with logoed studs:
+	// stud.dat -> stud-logo.dat
+	// stud2.dat -> stud-logo2.dat
+	if (gl_logostuds && (flags & LDSubfile::RendererInline))
+	{	if (getName() == "stud.dat" && g_logoedStud)
+			return g_logoedStud->inlineContents (flags);
+
+		elif (getName() == "stud2.dat" && g_logoedStud2)
+		return g_logoedStud2->inlineContents (flags);
+	}
+
+	QList<LDObject*> objs, objcache;
+
+	bool deep = flags & LDSubfile::DeepInline,
+		 doCache = flags & LDSubfile::CacheInline;
+
+	// If we have this cached, just clone that
+	if (deep && getCache().size())
+	{	for (LDObject* obj : getCache())
+			objs << obj->clone();
+	}
+	else
+	{	if (!deep)
+			doCache = false;
+
+		for (LDObject* obj : getObjects())
+		{	// Skip those without scemantic meaning
+			if (!obj->isScemantic())
+				continue;
+
+			// Got another sub-file reference, inline it if we're deep-inlining. If not,
+			// just add it into the objects normally. Also, we only cache immediate
+			// subfiles and this is not one. Yay, recursion!
+			if (deep && obj->getType() == LDObject::Subfile)
+			{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
+
+				// We only want to cache immediate subfiles, so shed the caching
+				// flag when recursing deeper in hierarchy.
+				QList<LDObject*> otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline));
+
+			for (LDObject * otherobj : otherobjs)
+				{	// Cache this object, if desired
+					if (doCache)
+						objcache << otherobj->clone();
+
+					objs << otherobj;
+				}
+			}
+			else
+			{	if (doCache)
+					objcache << obj->clone();
+
+				objs << obj->clone();
+			}
+		}
+
+		if (doCache)
+			setCache (objcache);
+	}
+
+	return objs;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDDocument* LDDocument::current()
+{	return m_curdoc;
+}
+
+// =============================================================================
+// Sets the given file as the current one on display. At some point in time this
+// was an operation completely unheard of. ;)
+//
+// FIXME: f can be temporarily null. This probably should not be the case.
+// -----------------------------------------------------------------------------
+void LDDocument::setCurrent (LDDocument* f)
+{	// Implicit files were loaded for caching purposes and must never be set
+	// current.
+	if (f && f->isImplicit())
+		return;
+
+	m_curdoc = f;
+
+	if (g_win && f)
+	{	// A ton of stuff needs to be updated
+		g_win->updateDocumentListItem (f);
+		g_win->buildObjList();
+		g_win->updateTitle();
+		g_win->R()->setFile (f);
+		g_win->R()->resetAllAngles();
+		g_win->R()->repaint();
+
+		log ("Changed file to %1", f->getShortName());
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+int LDDocument::countExplicitFiles()
+{	int count = 0;
+
+	for (LDDocument* f : g_loadedFiles)
+		if (f->isImplicit() == false)
+			count++;
+
+	return count;
+}
+
+// =============================================================================
+// This little beauty closes the initial file that was open at first when opening
+// a new file over it.
+// -----------------------------------------------------------------------------
+void LDDocument::closeInitialFile()
+{	if (
+		countExplicitFiles() == 2 &&
+		g_loadedFiles[0]->getName() == "" &&
+		!g_loadedFiles[0]->hasUnsavedChanges()
+	)
+		delete g_loadedFiles[0];
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void loadLogoedStuds()
+{	log ("Loading logoed studs...\n");
+
+	delete g_logoedStud;
+	delete g_logoedStud2;
+
+	g_logoedStud = openDocument ("stud-logo.dat", true);
+	g_logoedStud2 = openDocument ("stud2-logo.dat", true);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDDocument::addToSelection (LDObject* obj) // [protected]
+{	if (obj->isSelected())
+		return;
+
+	assert (obj->getFile() == this);
+	m_sel << obj;
+	obj->setSelected (true);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDDocument::removeFromSelection (LDObject* obj) // [protected]
+{	if (!obj->isSelected())
+		return;
+
+	assert (obj->getFile() == this);
+	m_sel.removeOne (obj);
+	obj->setSelected (false);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDDocument::clearSelection()
+{	for (LDObject* obj : m_sel)
+		removeFromSelection (obj);
+
+	assert (m_sel.isEmpty());
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+const QList<LDObject*>& LDDocument::getSelection() const
+{	return m_sel;
+}
\ No newline at end of file
--- a/src/document.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1186 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  file.cpp: File I/O and management.
- *  - File loading, parsing, manipulation, saving, closing.
- *  - LDraw path verification.
- */
-
-#include <QMessageBox>
-#include <QFileDialog>
-#include <QDir>
-#include <QApplication>
-#include "main.h"
-#include "config.h"
-#include "document.h"
-#include "misc.h"
-#include "gui.h"
-#include "history.h"
-#include "dialogs.h"
-#include "gldraw.h"
-#include "moc_document.cpp"
-
-cfg (String, io_ldpath, "");
-cfg (List, io_recentfiles, {});
-extern_cfg (String, net_downloadpath);
-extern_cfg (Bool, gl_logostuds);
-
-static bool g_loadingMainFile = false;
-static const int g_MaxRecentFiles = 5;
-static bool g_aborted = false;
-static LDDocument* g_logoedStud = null;
-static LDDocument* g_logoedStud2 = null;
-
-LDDocument* LDDocument::m_curdoc = null;
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-namespace LDPaths
-{	static str pathError;
-
-	struct
-	{	str LDConfigPath;
-		str partsPath, primsPath;
-	} pathInfo;
-
-	void initPaths()
-	{	if (!tryConfigure (io_ldpath))
-		{	LDrawPathDialog dlg (false);
-
-			if (!dlg.exec())
-				exit (0);
-
-			io_ldpath = dlg.filename();
-		}
-	}
-
-	bool tryConfigure (str path)
-	{	QDir dir;
-
-		if (!dir.cd (path))
-		{	pathError = "Directory does not exist.";
-			return false;
-		}
-
-		QStringList mustHave = { "LDConfig.ldr", "parts", "p" };
-		QStringList contents = dir.entryList (mustHave);
-
-		if (contents.size() != mustHave.size())
-		{	pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/.";
-			return false;
-		}
-
-		pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path);
-		pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path);
-		pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path);
-
-		return true;
-	}
-
-	// Accessors
-	str getError()
-	{	return pathError;
-	}
-
-	str ldconfig()
-	{	return pathInfo.LDConfigPath;
-	}
-
-	str prims()
-	{	return pathInfo.primsPath;
-	}
-
-	str parts()
-	{	return pathInfo.partsPath;
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument::LDDocument()
-{	setImplicit (true);
-	setSavePosition (-1);
-	setListItem (null);
-	setHistory (new History);
-	m_History->setFile (this);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument::~LDDocument()
-{	// Clear everything from the model
-	for (LDObject* obj : getObjects())
-		delete obj;
-
-	// Clear the cache as well
-	for (LDObject* obj : getCache())
-		delete obj;
-
-	delete m_History;
-
-	// Remove this file from the list of files
-	g_loadedFiles.removeOne (this);
-
-	// If we just closed the current file, we need to set the current
-	// file as something else.
-	if (this == getCurrentDocument())
-	{	bool found = false;
-
-		// Try find an explicitly loaded file - if we can't find one,
-		// we need to create a new file to switch to.
-		for (LDDocument* file : g_loadedFiles)
-		{	if (!file->isImplicit())
-			{	LDDocument::setCurrent (file);
-				found = true;
-				break;
-			}
-		}
-
-		if (!found)
-			newFile();
-	}
-
-	g_win->updateDocumentList();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument* findDocument (str name)
-{	for (LDDocument * file : g_loadedFiles)
-		if (!file->getName().isEmpty() && file->getShortName() == name)
-			return file;
-
-	return null;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str dirname (str path)
-{	long lastpos = path.lastIndexOf (DIRSLASH);
-
-	if (lastpos > 0)
-		return path.left (lastpos);
-
-#ifndef _WIN32
-
-	if (path[0] == DIRSLASH_CHAR)
-		return DIRSLASH;
-
-#endif // _WIN32
-
-	return "";
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str basename (str path)
-{	long lastpos = path.lastIndexOf (DIRSLASH);
-
-	if (lastpos != -1)
-		return path.mid (lastpos + 1);
-
-	return path;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-File* openLDrawFile (str relpath, bool subdirs)
-{	log ("Opening %1...\n", relpath);
-	File* f = new File;
-	str fullPath;
-
-	// LDraw models use Windows-style path separators. If we're not on Windows,
-	// replace the path separator now before opening any files.
-#ifndef WIN32
-	relpath.replace ("\\", "/");
-#endif // WIN32
-
-	if (getCurrentDocument())
-	{	// First, try find the file in the current model's file path. We want a file
-		// in the immediate vicinity of the current model to override stock LDraw stuff.
-		str partpath = fmt ("%1" DIRSLASH "%2", dirname (getCurrentDocument()->getName()), relpath);
-
-		if (f->open (partpath, File::Read))
-			return f;
-	}
-
-	if (f->open (relpath, File::Read))
-		return f;
-
-	// Try with just the LDraw path first
-	fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath);
-
-	if (f->open (fullPath, File::Read))
-		return f;
-
-	if (subdirs)
-	{	// Look in sub-directories: parts and p. Also look in net_downloadpath, since that's
-		// where we download parts from the PT to.
-		for (const str& topdir : initlist<str> ({ io_ldpath, net_downloadpath }))
-		{	for (const str& subdir : initlist<str> ({ "parts", "p" }))
-			{	fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath);
-
-				if (f->open (fullPath, File::Read))
-					return f;
-			}
-		}
-	}
-
-	// Did not find the file.
-	log ("Could not find %1.\n", relpath);
-	delete f;
-	return null;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDFileLoader::start()
-{	setDone (false);
-	setProgress (0);
-	setAborted (false);
-
-	if (isOnForeground())
-	{	g_aborted = false;
-
-		// Show a progress dialog if we're loading the main document.here so we can
-		// show progress updates and keep the WM posted that we're still here.
-		// Of course we cannot exec() the dialog because then the dialog would
-		// block.
-		dlg = new OpenProgressDialog (g_win);
-		dlg->setNumLines (getLines().size());
-		dlg->setModal (true);
-		dlg->show();
-
-		// Connect the loader in so we can show updates
-		connect (this, SIGNAL (workDone()), dlg, SLOT (accept()));
-		connect (dlg, SIGNAL (rejected()), this, SLOT (abort()));
-	}
-	else
-		dlg = null;
-
-	// Begin working
-	work (0);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDFileLoader::work (int i)
-{	// User wishes to abort, so stop here now.
-	if (isAborted())
-	{	for (LDObject* obj : m_Objects)
-			delete obj;
-
-		m_Objects.clear();
-		setDone (true);
-		return;
-	}
-
-	// Parse up to 300 lines per iteration
-	int max = i + 300;
-
-	for (; i < max && i < (int) getLines().size(); ++i)
-	{	str line = getLines()[i];
-
-		// Trim the trailing newline
-		QChar c;
-
-		while (!line.isEmpty() && ((c = line[line.length() - 1]) == '\n' || c == '\r'))
-			line.chop (1);
-
-		LDObject* obj = parseLine (line);
-
-		// Check for parse errors and warn about tthem
-		if (obj->getType() == LDObject::Error)
-		{	log ("Couldn't parse line #%1: %2", getProgress() + 1, static_cast<LDError*> (obj)->reason);
-
-			if (getWarnings() != null)
-				(*getWarnings())++;
-		}
-
-		m_Objects << obj;
-		setProgress (i);
-
-		// If we have a dialog pointer, update the progress now
-		if (isOnForeground())
-			dlg->updateProgress (i);
-	}
-
-	// If we're done now, tell the environment we're done and stop.
-	if (i >= ((int) getLines().size()) - 1)
-	{	emit workDone();
-		setDone (true);
-		return;
-	}
-
-	// Otherwise, continue, by recursing back.
-	if (!isDone())
-	{	// If we have a dialog to show progress output to, we cannot just call
-		// work() again immediately as the dialog needs some processor cycles as
-		// well. Thus, take a detour through the event loop by using the
-		// meta-object system.
-		//
-		// This terminates the loop here and control goes back to the function
-		// which called the file loader. It will keep processing the event loop
-		// until we're ready (see loadFileContents), thus the event loop will
-		// eventually catch the invokation we throw here and send us back. Though
-		// it's not technically recursion anymore, more like a for loop. :P
-		if (isOnForeground())
-			QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i));
-		else
-			work (i + 1);
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDFileLoader::abort()
-{	setAborted (true);
-
-	if (isOnForeground())
-		g_aborted = true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok)
-{	QList<str> lines;
-	QList<LDObject*> objs;
-
-	if (numWarnings)
-		*numWarnings = 0;
-
-	// Calculate the amount of lines
-	for (str line : *f)
-		lines << line;
-
-	f->rewind();
-
-	LDFileLoader* loader = new LDFileLoader;
-	loader->setWarnings (numWarnings);
-	loader->setLines (lines);
-	loader->setOnForeground (g_loadingMainFile);
-	loader->start();
-
-	// After start() returns, if the loader isn't done yet, it's delaying
-	// its next iteration through the event loop. We need to catch this here
-	// by telling the event loop to tick, which will tick the file loader again.
-	// We keep doing this until the file loader is ready.
-	while (loader->isDone() == false)
-		qApp->processEvents();
-
-	// If we wanted the success value, supply that now
-	if (ok)
-		*ok = !loader->isAborted();
-
-	objs = loader->getObjects();
-	return objs;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument* openDocument (str path, bool search)
-{	// Convert the file name to lowercase since some parts contain uppercase
-	// file names. I'll assume here that the library will always use lowercase
-	// file names for the actual parts..
-	File* f;
-
-	if (search)
-		f = openLDrawFile (path.toLower(), true);
-	else
-	{	f = new File (path, File::Read);
-
-		if (!*f)
-		{	delete f;
-			return null;
-		}
-	}
-
-	if (!f)
-		return null;
-
-	LDDocument* load = new LDDocument;
-	load->setName (path);
-
-	int numWarnings;
-	bool ok;
-	QList<LDObject*> objs = loadFileContents (f, &numWarnings, &ok);
-
-	if (!ok)
-		return null;
-
-	for (LDObject* obj : objs)
-		load->addObject (obj);
-
-	delete f;
-	g_loadedFiles << load;
-
-	if (g_loadingMainFile)
-	{	LDDocument::setCurrent (load);
-		g_win->R()->setFile (load);
-		log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings);
-	}
-
-	return load;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool LDDocument::isSafeToClose()
-{	typedef QMessageBox msgbox;
-	setlocale (LC_ALL, "C");
-
-	// If we have unsaved changes, warn and give the option of saving.
-	if (hasUnsavedChanges())
-	{	str message = fmt ("There are unsaved changes to %1. Should it be saved?",
-						   (getName().length() > 0) ? getName() : "<anonymous>");
-
-		int button = msgbox::question (g_win, "Unsaved Changes", message,
-									   (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel);
-
-		switch (button)
-		{	case msgbox::Yes:
-
-				// If we don't have a file path yet, we have to ask the user for one.
-				if (getName().length() == 0)
-				{	str newpath = QFileDialog::getSaveFileName (g_win, "Save As",
-								  getCurrentDocument()->getName(), "LDraw files (*.dat *.ldr)");
-
-					if (newpath.length() == 0)
-						return false;
-
-					setName (newpath);
-				}
-
-				if (!save())
-				{	message = fmt (QObject::tr ("Failed to save %1: %2\nDo you still want to close?"),
-								   getName(), strerror (errno));
-
-					if (msgbox::critical (g_win, "Save Failure", message,
-										  (msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No)
-					{	return false;
-					}
-				}
-
-				break;
-
-			case msgbox::Cancel:
-				return false;
-
-			default:
-				break;
-		}
-	}
-
-	return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void closeAll()
-{	// Remove all loaded files and the objects they contain
-	QList<LDDocument*> files = g_loadedFiles;
-
-for (LDDocument * file : files)
-		delete file;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void newFile()
-{	// Create a new anonymous file and set it to our current
-	LDDocument* f = new LDDocument;
-	f->setName ("");
-	f->setImplicit (false);
-	g_loadedFiles << f;
-	LDDocument::setCurrent (f);
-	LDDocument::closeInitialFile();
-	g_win->R()->setFile (f);
-	g_win->doFullRefresh();
-	g_win->updateTitle();
-	g_win->updateActions();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void addRecentFile (str path)
-{	alias rfiles = io_recentfiles.value;
-	int idx = rfiles.indexOf (path);
-
-	// If this file already is in the list, pop it out.
-	if (idx != -1)
-	{	if (rfiles.size() == 1)
-			return; // only recent file - abort and do nothing
-
-		// Pop it out.
-		rfiles.removeAt (idx);
-	}
-
-	// If there's too many recent files, drop one out.
-	while (rfiles.size() > (g_MaxRecentFiles - 1))
-		rfiles.removeAt (0);
-
-	// Add the file
-	rfiles << path;
-
-	Config::save();
-	g_win->updateRecentFilesMenu();
-}
-
-// =============================================================================
-// Open an LDraw file and set it as the main model
-// -----------------------------------------------------------------------------
-void openMainFile (str path)
-{	g_loadingMainFile = true;
-	LDDocument* file = openDocument (path, false);
-
-	if (!file)
-	{	// Loading failed, thus drop down to a new file since we
-		// closed everything prior.
-		newFile();
-
-		if (!g_aborted)
-		{	// Tell the user loading failed.
-			setlocale (LC_ALL, "C");
-			critical (fmt (QObject::tr ("Failed to open %1: %2"), path, strerror (errno)));
-		}
-
-		g_loadingMainFile = false;
-		return;
-	}
-
-	file->setImplicit (false);
-
-	// If we have an anonymous, unchanged file open as the only open file
-	// (aside of the one we just opened), close it now.
-	LDDocument::closeInitialFile();
-
-	// Rebuild the object tree view now.
-	LDDocument::setCurrent (file);
-	g_win->doFullRefresh();
-
-	// Add it to the recent files list.
-	addRecentFile (path);
-	g_loadingMainFile = false;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool LDDocument::save (str savepath)
-{	if (!savepath.length())
-		savepath = getName();
-
-	File f (savepath, File::Write);
-
-	if (!f)
-		return false;
-
-	// If the second object in the list holds the file name, update that now.
-	// Only do this if the file is explicitly open. If it's saved into a directory
-	// called "s" or "48", prepend that into the name.
-	LDComment* fpathComment = null;
-	LDObject* first = getObject (1);
-
-	if (!isImplicit() && first != null && first->getType() == LDObject::Comment)
-	{	fpathComment = static_cast<LDComment*> (first);
-
-		if (fpathComment->text.left (6) == "Name: ")
-		{	str newname;
-			str dir = basename (dirname (savepath));
-
-			if (dir == "s" || dir == "48")
-				newname = dir + "\\";
-
-			newname += basename (savepath);
-			fpathComment->text = fmt ("Name: %1", newname);
-			g_win->buildObjList();
-		}
-	}
-
-	// File is open, now save the model to it. Note that LDraw requires files to
-	// have DOS line endings, so we terminate the lines with \r\n.
-	for (LDObject* obj : getObjects())
-		f.write (obj->raw() + "\r\n");
-
-	// File is saved, now clean up.
-	f.close();
-
-	// We have successfully saved, update the save position now.
-	setSavePosition (getHistory()->getPosition());
-	setName (savepath);
-
-	g_win->updateDocumentListItem (this);
-	g_win->updateTitle();
-	return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-#define CHECK_TOKEN_COUNT(N) \
-	if (tokens.size() != N) \
-		return new LDError (line, "Bad amount of tokens");
-
-#define CHECK_TOKEN_NUMBERS(MIN, MAX) \
-	for (int i = MIN; i <= MAX; ++i) \
-		if (!numeric (tokens[i])) \
-			return new LDError (line, fmt ("Token #%1 was `%2`, expected a number", (i + 1), tokens[i]));
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static vertex parseVertex (QStringList& s, const int n)
-{	vertex v;
-
-	for (const Axis ax : g_Axes)
-		v[ax] = s[n + ax].toDouble();
-
-	return v;
-}
-
-// =============================================================================
-// This is the LDraw code parser function. It takes in a string containing LDraw
-// code and returns the object parsed from it. parseLine never returns null,
-// the object will be LDError if it could not be parsed properly.
-// -----------------------------------------------------------------------------
-LDObject* parseLine (str line)
-{	QStringList tokens = line.split (" ", str::SkipEmptyParts);
-
-	if (tokens.size() <= 0)
-	{	// Line was empty, or only consisted of whitespace
-		return new LDEmpty;
-	}
-
-	if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false)
-		return new LDError (line, "Illogical line code");
-
-	int num = tokens[0][0].digitValue();
-
-	switch (num)
-	{	case 0:
-		{	// Comment
-			str comm = line.mid (line.indexOf ("0") + 1);
-
-			// Remove any leading whitespace
-			while (comm[0] == ' ')
-				comm.remove (0, 1);
-
-			// Handle BFC statements
-			if (tokens.size() > 2 && tokens[1] == "BFC")
-			{	for (int i = 0; i < LDBFC::NumStatements; ++i)
-					if (comm == fmt ("BFC %1", LDBFC::statements [i]))
-						return new LDBFC ( (LDBFC::Type) i);
-
-				// MLCAD is notorious for stuffing these statements in parts it
-				// creates. The above block only handles valid statements, so we
-				// need to handle MLCAD-style invertnext, clip and noclip separately.
-				struct
-				{	const char* a;
-					LDBFC::Type b;
-				} BFCData[] =
-				{	{ "INVERTNEXT", LDBFC::InvertNext },
-					{ "NOCLIP", LDBFC::NoClip },
-					{ "CLIP", LDBFC::Clip }
-				};
-
-			for (const auto & i : BFCData)
-					if (comm == fmt ("BFC CERTIFY %1", i.a))
-						return new LDBFC (i.b);
-			}
-
-			if (tokens.size() > 2 && tokens[1] == "!LDFORGE")
-			{	// Handle LDForge-specific types, they're embedded into comments too
-				if (tokens[2] == "VERTEX")
-				{	// Vertex (0 !LDFORGE VERTEX)
-					CHECK_TOKEN_COUNT (7)
-					CHECK_TOKEN_NUMBERS (3, 6)
-
-					LDVertex* obj = new LDVertex;
-					obj->setColor (tokens[3].toLong());
-
-				for (const Axis ax : g_Axes)
-						obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6
-
-					return obj;
-				} elif (tokens[2] == "OVERLAY")
-
-				{	CHECK_TOKEN_COUNT (9);
-					CHECK_TOKEN_NUMBERS (5, 8)
-
-					LDOverlay* obj = new LDOverlay;
-					obj->setFileName (tokens[3]);
-					obj->setCamera (tokens[4].toLong());
-					obj->setX (tokens[5].toLong());
-					obj->setY (tokens[6].toLong());
-					obj->setWidth (tokens[7].toLong());
-					obj->setHeight (tokens[8].toLong());
-					return obj;
-				}
-			}
-
-			// Just a regular comment:
-			LDComment* obj = new LDComment;
-			obj->text = comm;
-			return obj;
-		}
-
-		case 1:
-		{	// Subfile
-			CHECK_TOKEN_COUNT (15)
-			CHECK_TOKEN_NUMBERS (1, 13)
-
-			// Try open the file. Disable g_loadingMainFile temporarily since we're
-			// not loading the main file now, but the subfile in question.
-			bool tmp = g_loadingMainFile;
-			g_loadingMainFile = false;
-			LDDocument* load = getDocument (tokens[14]);
-			g_loadingMainFile = tmp;
-
-			// If we cannot open the file, mark it an error
-			if (!load)
-			{	LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14]));
-				obj->setFileReferenced (tokens[14]);
-				return obj;
-			}
-
-			LDSubfile* obj = new LDSubfile;
-			obj->setColor (tokens[1].toLong());
-			obj->setPosition (parseVertex (tokens, 2));  // 2 - 4
-
-			matrix transform;
-
-			for (int i = 0; i < 9; ++i)
-				transform[i] = tokens[i + 5].toDouble(); // 5 - 13
-
-			obj->setTransform (transform);
-			obj->setFileInfo (load);
-			return obj;
-		}
-
-		case 2:
-		{	CHECK_TOKEN_COUNT (8)
-			CHECK_TOKEN_NUMBERS (1, 7)
-
-			// Line
-			LDLine* obj = new LDLine;
-			obj->setColor (tokens[1].toLong());
-
-			for (int i = 0; i < 2; ++i)
-				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 7
-
-			return obj;
-		}
-
-		case 3:
-		{	CHECK_TOKEN_COUNT (11)
-			CHECK_TOKEN_NUMBERS (1, 10)
-
-			// Triangle
-			LDTriangle* obj = new LDTriangle;
-			obj->setColor (tokens[1].toLong());
-
-			for (int i = 0; i < 3; ++i)
-				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 10
-
-			return obj;
-		}
-
-		case 4:
-		case 5:
-		{	CHECK_TOKEN_COUNT (14)
-			CHECK_TOKEN_NUMBERS (1, 13)
-
-			// Quadrilateral / Conditional line
-			LDObject* obj;
-
-			if (num == 4)
-				obj = new LDQuad;
-			else
-				obj = new LDCondLine;
-
-			obj->setColor (tokens[1].toLong());
-
-			for (int i = 0; i < 4; ++i)
-				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3)));   // 2 - 13
-
-			return obj;
-		}
-
-		default: // Strange line we couldn't parse
-			return new LDError (line, "Unknown line code number");
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument* getDocument (str filename)
-{	// Try find the file in the list of loaded files
-	LDDocument* doc = findDocument (filename);
-
-	// If it's not loaded, try open it
-	if (!doc)
-		doc = openDocument (filename, true);
-
-	return doc;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void reloadAllSubfiles()
-{	if (!getCurrentDocument())
-		return;
-
-	g_loadedFiles.clear();
-	g_loadedFiles << getCurrentDocument();
-
-	// Go through all objects in the current file and reload the subfiles
-	for (LDObject* obj : getCurrentDocument()->getObjects())
-	{	if (obj->getType() == LDObject::Subfile)
-		{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDDocument* fileInfo = getDocument (ref->getFileInfo()->getName());
-
-			if (fileInfo)
-				ref->setFileInfo (fileInfo);
-			else
-				ref->replace (new LDError (ref->raw(), "Could not open referred file"));
-		}
-
-		// Reparse gibberish files. It could be that they are invalid because
-		// of loading errors. Circumstances may be different now.
-		if (obj->getType() == LDObject::Error)
-			obj->replace (parseLine (static_cast<LDError*> (obj)->contents));
-	}
-
-	// Close all files left unused
-	LDDocument::closeUnused();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-int LDDocument::addObject (LDObject* obj)
-{	getHistory()->add (new AddHistory (getObjects().size(), obj));
-	m_Objects << obj;
-
-	if (obj->getType() == LDObject::Vertex)
-		m_Vertices << obj;
-
-	obj->setFile (this);
-	return getObjectCount() - 1;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::addObjects (const QList<LDObject*> objs)
-{	for (LDObject * obj : objs)
-		if (obj)
-			addObject (obj);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::insertObj (int pos, LDObject* obj)
-{	getHistory()->add (new AddHistory (pos, obj));
-	m_Objects.insert (pos, obj);
-	obj->setFile (this);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::forgetObject (LDObject* obj)
-{	int idx = obj->getIndex();
-	getHistory()->add (new DelHistory (idx, obj));
-	m_Objects.removeAt (idx);
-	obj->setFile (null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool safeToCloseAll()
-{	for (LDDocument* f : g_loadedFiles)
-		if (!f->isSafeToClose())
-			return false;
-
-	return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::setObject (int idx, LDObject* obj)
-{	assert (idx < m_Objects.size());
-
-	// Mark this change to history
-	str oldcode = getObject (idx)->raw();
-	str newcode = obj->raw();
-	*m_History << new EditHistory (idx, oldcode, newcode);
-
-	obj->setFile (this);
-	m_Objects[idx] = obj;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static QList<LDDocument*> getFilesUsed (LDDocument* node)
-{	QList<LDDocument*> filesUsed;
-
-	for (LDObject* obj : node->getObjects())
-	{	if (obj->getType() != LDObject::Subfile)
-			continue;
-
-		LDSubfile* ref = static_cast<LDSubfile*> (obj);
-		filesUsed << ref->getFileInfo();
-		filesUsed << getFilesUsed (ref->getFileInfo());
-	}
-
-	return filesUsed;
-}
-
-// =============================================================================
-// Find out which files are unused and close them.
-// -----------------------------------------------------------------------------
-void LDDocument::closeUnused()
-{	QList<LDDocument*> filesUsed = getFilesUsed (getCurrentDocument());
-
-	// Anything that's explicitly opened must not be closed
-	for (LDDocument* file : g_loadedFiles)
-		if (!file->isImplicit())
-			filesUsed << file;
-
-	// Remove duplicated entries
-	removeDuplicates (filesUsed);
-
-	// Close all open files that aren't in filesUsed
-	for (LDDocument* file : g_loadedFiles)
-	{	bool isused = false;
-
-		for (LDDocument* usedFile : filesUsed)
-		{	if (file == usedFile)
-			{	isused = true;
-				break;
-			}
-		}
-
-		if (!isused)
-			delete file;
-	}
-
-	g_loadedFiles.clear();
-	g_loadedFiles << filesUsed;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObject* LDDocument::getObject (int pos) const
-{	if (m_Objects.size() <= pos)
-		return null;
-
-	return m_Objects[pos];
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-int LDDocument::getObjectCount() const
-{	return getObjects().size();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool LDDocument::hasUnsavedChanges() const
-{	return !isImplicit() && getHistory()->getPosition() != getSavePosition();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDDocument::getShortName()
-{	if (!getName().isEmpty())
-		return basename (getName());
-
-	if (!getDefaultName().isEmpty())
-		return getDefaultName();
-
-	return tr ("<anonymous>");
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QList<LDObject*> LDDocument::inlineContents (LDSubfile::InlineFlags flags)
-{	// Possibly substitute with logoed studs:
-	// stud.dat -> stud-logo.dat
-	// stud2.dat -> stud-logo2.dat
-	if (gl_logostuds && (flags & LDSubfile::RendererInline))
-	{	if (getName() == "stud.dat" && g_logoedStud)
-			return g_logoedStud->inlineContents (flags);
-
-		elif (getName() == "stud2.dat" && g_logoedStud2)
-		return g_logoedStud2->inlineContents (flags);
-	}
-
-	QList<LDObject*> objs, objcache;
-
-	bool deep = flags & LDSubfile::DeepInline,
-		 doCache = flags & LDSubfile::CacheInline;
-
-	// If we have this cached, just clone that
-	if (deep && getCache().size())
-	{	for (LDObject* obj : getCache())
-			objs << obj->clone();
-	}
-	else
-	{	if (!deep)
-			doCache = false;
-
-		for (LDObject* obj : getObjects())
-		{	// Skip those without scemantic meaning
-			if (!obj->isScemantic())
-				continue;
-
-			// Got another sub-file reference, inline it if we're deep-inlining. If not,
-			// just add it into the objects normally. Also, we only cache immediate
-			// subfiles and this is not one. Yay, recursion!
-			if (deep && obj->getType() == LDObject::Subfile)
-			{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
-
-				// We only want to cache immediate subfiles, so shed the caching
-				// flag when recursing deeper in hierarchy.
-				QList<LDObject*> otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline));
-
-			for (LDObject * otherobj : otherobjs)
-				{	// Cache this object, if desired
-					if (doCache)
-						objcache << otherobj->clone();
-
-					objs << otherobj;
-				}
-			}
-			else
-			{	if (doCache)
-					objcache << obj->clone();
-
-				objs << obj->clone();
-			}
-		}
-
-		if (doCache)
-			setCache (objcache);
-	}
-
-	return objs;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDDocument* LDDocument::current()
-{	return m_curdoc;
-}
-
-// =============================================================================
-// Sets the given file as the current one on display. At some point in time this
-// was an operation completely unheard of. ;)
-//
-// FIXME: f can be temporarily null. This probably should not be the case.
-// -----------------------------------------------------------------------------
-void LDDocument::setCurrent (LDDocument* f)
-{	// Implicit files were loaded for caching purposes and must never be set
-	// current.
-	if (f && f->isImplicit())
-		return;
-
-	m_curdoc = f;
-
-	if (g_win && f)
-	{	// A ton of stuff needs to be updated
-		g_win->updateDocumentListItem (f);
-		g_win->buildObjList();
-		g_win->updateTitle();
-		g_win->R()->setFile (f);
-		g_win->R()->resetAllAngles();
-		g_win->R()->repaint();
-
-		log ("Changed file to %1", f->getShortName());
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-int LDDocument::countExplicitFiles()
-{	int count = 0;
-
-	for (LDDocument* f : g_loadedFiles)
-		if (f->isImplicit() == false)
-			count++;
-
-	return count;
-}
-
-// =============================================================================
-// This little beauty closes the initial file that was open at first when opening
-// a new file over it.
-// -----------------------------------------------------------------------------
-void LDDocument::closeInitialFile()
-{	if (
-		countExplicitFiles() == 2 &&
-		g_loadedFiles[0]->getName() == "" &&
-		!g_loadedFiles[0]->hasUnsavedChanges()
-	)
-		delete g_loadedFiles[0];
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void loadLogoedStuds()
-{	log ("Loading logoed studs...\n");
-
-	delete g_logoedStud;
-	delete g_logoedStud2;
-
-	g_logoedStud = openDocument ("stud-logo.dat", true);
-	g_logoedStud2 = openDocument ("stud2-logo.dat", true);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::addToSelection (LDObject* obj) // [protected]
-{	if (obj->isSelected())
-		return;
-
-	assert (obj->getFile() == this);
-	m_sel << obj;
-	obj->setSelected (true);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::removeFromSelection (LDObject* obj) // [protected]
-{	if (!obj->isSelected())
-		return;
-
-	assert (obj->getFile() == this);
-	m_sel.removeOne (obj);
-	obj->setSelected (false);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDDocument::clearSelection()
-{	for (LDObject* obj : m_sel)
-		removeFromSelection (obj);
-
-	assert (m_sel.isEmpty());
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-const QList<LDObject*>& LDDocument::getSelection() const
-{	return m_sel;
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/download.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,496 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QDir>
+#include <QProgressBar>
+#include <QPushButton>
+#include "download.h"
+#include "ui_downloadfrom.h"
+#include "types.h"
+#include "gui.h"
+#include "document.h"
+#include "gldraw.h"
+#include "configDialog.h"
+#include "moc_download.cpp"
+
+cfg (String,	net_downloadpath,	"");
+cfg (Bool,		net_guesspaths,	true);
+cfg (Bool,		net_autoclose,		true);
+
+const str g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/");
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PartDownloader::staticBegin()
+{	str 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();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str PartDownloader::getDownloadPath()
+{	str path = net_downloadpath;
+
+#if DIRSLASH_CHAR != '/'
+	path.replace (DIRSLASH, "/");
+#endif
+
+	return path;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+PartDownloader::PartDownloader (QWidget* parent) : QDialog (parent)
+{	setInterface (new Ui_DownloadFrom);
+	getInterface()->setupUi (this);
+	getInterface()->fname->setFocus();
+	getInterface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch);
+
+	setDownloadButton (new QPushButton (tr ("Download")));
+	getInterface()->buttonBox->addButton (getDownloadButton(), QDialogButtonBox::ActionRole);
+	getButton (Abort)->setEnabled (false);
+
+	connect (getInterface()->source, SIGNAL (currentIndexChanged (int)),
+		this, SLOT (sourceChanged (int)));
+	connect (getInterface()->buttonBox, SIGNAL (clicked (QAbstractButton*)),
+		this, SLOT (buttonClicked (QAbstractButton*)));
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+PartDownloader::~PartDownloader()
+{	delete getInterface();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str PartDownloader::getURL() const
+{	const Source src = getSource();
+	str dest;
+
+	switch (src)
+	{	case PartsTracker:
+			dest = getInterface()->fname->text();
+			modifyDestination (dest);
+			return g_unofficialLibraryURL + dest;
+
+		case CustomURL:
+			return getInterface()->fname->text();
+	}
+
+	// Shouldn't happen
+	return "";
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PartDownloader::modifyDestination (str& 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. */
+	str partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*";
+	str 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) getInterface()->source->currentIndex();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PartDownloader::sourceChanged (int i)
+{	if (i == CustomURL)
+		getInterface()->fileNameLabel->setText (tr ("URL:"));
+	else
+		getInterface()->fileNameLabel->setText (tr ("File name:"));
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PartDownloader::buttonClicked (QAbstractButton* btn)
+{	if (btn == getButton (Close))
+	{	reject();
+	}
+	elif (btn == getButton (Abort))
+	{	setAborted (true);
+
+		for (PartDownloadRequest* req : getRequests())
+			req->abort();
+	}
+	elif (btn == getButton (Download))
+	{	str dest = getInterface()->fname->text();
+		setPrimaryFile (null);
+		setAborted (false);
+
+		if (getSource() == CustomURL)
+			dest = basename (getURL());
+
+		modifyDestination (dest);
+
+		if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest))
+		{	const str overwritemsg = fmt (tr ("%1 already exists in download directory. Overwrite?"), dest);
+			if (!confirm (tr ("Overwrite?"), overwritemsg))
+				return;
+		}
+
+		getDownloadButton()->setEnabled (false);
+		getInterface()->progress->setEnabled (true);
+		getInterface()->fname->setEnabled (false);
+		getInterface()->source->setEnabled (false);
+		downloadFile (dest, getURL(), true);
+		getButton (Close)->setEnabled (false);
+		getButton (Abort)->setEnabled (true);
+		getButton (Download)->setEnabled (false);
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PartDownloader::downloadFile (str dest, str url, bool primary)
+{	const int row = getInterface()->progress->rowCount();
+
+	// Don't download files repeadetly.
+	if (getFilesToDownload().indexOf (dest) != -1)
+		return;
+
+	modifyDestination (dest);
+	log ("DOWNLOAD: %1 -> %2\n", url, PartDownloader::getDownloadPath() + DIRSLASH + dest);
+	PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this);
+
+	pushToFilesToDownload (dest);
+	pushToRequests (req);
+	getInterface()->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 : getRequests())
+	{	if (!req->isFinished())
+			return;
+
+		if (req->getState() == PartDownloadRequest::EFailed)
+			failed = true;
+	}
+
+	for (PartDownloadRequest* req : getRequests())
+		delete req;
+
+	clearRequests();
+
+	// Update everything now
+	if (getPrimaryFile())
+	{	LDDocument::setCurrent (getPrimaryFile());
+		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 getDownloadButton();
+
+		case Abort:
+			return qobject_cast<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Abort));
+
+		case Close:
+			return qobject_cast<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Close));
+	}
+
+	return null;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+PartDownloadRequest::PartDownloadRequest (str url, str 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_NAM (new QNetworkAccessManager),
+	m_FirstUpdate (true),
+	m_Primary (primary),
+	m_FilePointer (null)
+{
+	// Make sure that we have a valid destination.
+	str dirpath = dirname (getFilePath());
+
+	QDir dir (dirpath);
+
+	if (!dir.exists())
+	{	log ("Creating %1...\n", dirpath);
+
+		if (!dir.mkpath (dirpath))
+			critical (fmt (tr ("Couldn't create the directory %1!"), dirpath));
+	}
+
+	setReply (getNAM()->get (QNetworkRequest (QUrl (url))));
+	connect (getReply(), SIGNAL (finished()), this, SLOT (downloadFinished()));
+	connect (getReply(), SIGNAL (readyRead()), this, SLOT (readyRead()));
+	connect (getReply(), SIGNAL (downloadProgress (qint64, qint64)),
+		this, SLOT (downloadProgress (qint64, qint64)));
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+PartDownloadRequest::~PartDownloadRequest() {}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PartDownloadRequest::updateToTable()
+{	const int		labelcol = PartDownloader::PartLabelColumn,
+						progcol = PartDownloader::ProgressColumn;
+	QTableWidget*	table = getPrompt()->getInterface()->progress;
+	QProgressBar*	prog;
+
+	switch (getState())
+	{	case ERequesting:
+		case EDownloading:
+		{	prog = qobject_cast<QProgressBar*> (table->cellWidget (getTableRow(), progcol));
+
+			if (!prog)
+			{	prog = new QProgressBar;
+				table->setCellWidget (getTableRow(), progcol, prog);
+			}
+
+			prog->setRange (0, getBytesTotal());
+			prog->setValue (getBytesRead());
+		} break;
+
+		case EFinished:
+		case EFailed:
+		{	const str text = (getState() == 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 (getTableRow(), progcol, lb);
+		} break;
+	}
+
+	QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (getTableRow(), labelcol));
+
+	if (isFirstUpdate())
+	{	lb = new QLabel (fmt ("<b>%1</b>", getDestinaton()), table);
+		table->setCellWidget (getTableRow(), 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 (getReply()->error() != QNetworkReply::NoError)
+	{	if (isPrimary() && !getPrompt()->isAborted())
+			critical (getReply()->errorString());
+
+		setState (EFailed);
+	}
+	elif (getState() != EFailed)
+		setState (EFinished);
+
+	setBytesRead (getBytesTotal());
+	updateToTable();
+
+	if (getFilePointer())
+	{	getFilePointer()->close();
+		delete getFilePointer();
+		setFilePointer (null);
+
+		if (getState() == EFailed)
+			QFile::remove (getFilePath());
+	}
+
+	if (getState() != EFinished)
+	{	getPrompt()->checkIfFinished();
+		return;
+	}
+
+	// Try to load this file now.
+	LDDocument* f = openDocument (getFilePath(), 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->getObjects())
+	{	LDError* err = dynamic_cast<LDError*> (obj);
+
+		if (!err || err->getFileReferenced().isEmpty())
+			continue;
+
+		str dest = err->getFileReferenced();
+		getPrompt()->modifyDestination (dest);
+		getPrompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false);
+	}
+
+	if (isPrimary())
+	{	addRecentFile (getFilePath());
+		getPrompt()->setPrimaryFile (f);
+	}
+
+	getPrompt()->checkIfFinished();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PartDownloadRequest::downloadProgress (int64 recv, int64 total)
+{	setBytesRead (recv);
+	setBytesTotal (total);
+	setState (EDownloading);
+	updateToTable();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PartDownloadRequest::readyRead()
+{	if (getState() == EFailed)
+		return;
+
+	if (getFilePointer() == null)
+	{	replaceInFilePath ("\\", "/");
+
+		// We have already asked the user whether we can overwrite so we're good
+		// to go here.
+		setFilePointer (new QFile (getFilePath().toLocal8Bit()));
+
+		if (!getFilePointer()->open (QIODevice::WriteOnly))
+		{	critical (fmt (tr ("Couldn't open %1 for writing: %2"), getFilePath(), strerror (errno)));
+			setState (EFailed);
+			getReply()->abort();
+			updateToTable();
+			getPrompt()->checkIfFinished();
+			return;
+		}
+	}
+
+	getFilePointer()->write (getReply()->readAll());
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool PartDownloadRequest::isFinished() const
+{	return getState() == EFinished || getState() == EFailed;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PartDownloadRequest::abort()
+{	getReply()->abort();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (DownloadFrom, 0)
+{	PartDownloader::staticBegin();
+}
--- a/src/download.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,496 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QNetworkAccessManager>
-#include <QNetworkRequest>
-#include <QNetworkReply>
-#include <QDir>
-#include <QProgressBar>
-#include <QPushButton>
-#include "download.h"
-#include "ui_downloadfrom.h"
-#include "types.h"
-#include "gui.h"
-#include "document.h"
-#include "gldraw.h"
-#include "configDialog.h"
-#include "moc_download.cpp"
-
-cfg (String,	net_downloadpath,	"");
-cfg (Bool,		net_guesspaths,	true);
-cfg (Bool,		net_autoclose,		true);
-
-const str g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/");
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PartDownloader::staticBegin()
-{	str 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();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str PartDownloader::getDownloadPath()
-{	str path = net_downloadpath;
-
-#if DIRSLASH_CHAR != '/'
-	path.replace (DIRSLASH, "/");
-#endif
-
-	return path;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-PartDownloader::PartDownloader (QWidget* parent) : QDialog (parent)
-{	setInterface (new Ui_DownloadFrom);
-	getInterface()->setupUi (this);
-	getInterface()->fname->setFocus();
-	getInterface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch);
-
-	setDownloadButton (new QPushButton (tr ("Download")));
-	getInterface()->buttonBox->addButton (getDownloadButton(), QDialogButtonBox::ActionRole);
-	getButton (Abort)->setEnabled (false);
-
-	connect (getInterface()->source, SIGNAL (currentIndexChanged (int)),
-		this, SLOT (sourceChanged (int)));
-	connect (getInterface()->buttonBox, SIGNAL (clicked (QAbstractButton*)),
-		this, SLOT (buttonClicked (QAbstractButton*)));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-PartDownloader::~PartDownloader()
-{	delete getInterface();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str PartDownloader::getURL() const
-{	const Source src = getSource();
-	str dest;
-
-	switch (src)
-	{	case PartsTracker:
-			dest = getInterface()->fname->text();
-			modifyDestination (dest);
-			return g_unofficialLibraryURL + dest;
-
-		case CustomURL:
-			return getInterface()->fname->text();
-	}
-
-	// Shouldn't happen
-	return "";
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PartDownloader::modifyDestination (str& 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. */
-	str partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*";
-	str 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) getInterface()->source->currentIndex();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PartDownloader::sourceChanged (int i)
-{	if (i == CustomURL)
-		getInterface()->fileNameLabel->setText (tr ("URL:"));
-	else
-		getInterface()->fileNameLabel->setText (tr ("File name:"));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PartDownloader::buttonClicked (QAbstractButton* btn)
-{	if (btn == getButton (Close))
-	{	reject();
-	}
-	elif (btn == getButton (Abort))
-	{	setAborted (true);
-
-		for (PartDownloadRequest* req : getRequests())
-			req->abort();
-	}
-	elif (btn == getButton (Download))
-	{	str dest = getInterface()->fname->text();
-		setPrimaryFile (null);
-		setAborted (false);
-
-		if (getSource() == CustomURL)
-			dest = basename (getURL());
-
-		modifyDestination (dest);
-
-		if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest))
-		{	const str overwritemsg = fmt (tr ("%1 already exists in download directory. Overwrite?"), dest);
-			if (!confirm (tr ("Overwrite?"), overwritemsg))
-				return;
-		}
-
-		getDownloadButton()->setEnabled (false);
-		getInterface()->progress->setEnabled (true);
-		getInterface()->fname->setEnabled (false);
-		getInterface()->source->setEnabled (false);
-		downloadFile (dest, getURL(), true);
-		getButton (Close)->setEnabled (false);
-		getButton (Abort)->setEnabled (true);
-		getButton (Download)->setEnabled (false);
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PartDownloader::downloadFile (str dest, str url, bool primary)
-{	const int row = getInterface()->progress->rowCount();
-
-	// Don't download files repeadetly.
-	if (getFilesToDownload().indexOf (dest) != -1)
-		return;
-
-	modifyDestination (dest);
-	log ("DOWNLOAD: %1 -> %2\n", url, PartDownloader::getDownloadPath() + DIRSLASH + dest);
-	PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this);
-
-	pushToFilesToDownload (dest);
-	pushToRequests (req);
-	getInterface()->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 : getRequests())
-	{	if (!req->isFinished())
-			return;
-
-		if (req->getState() == PartDownloadRequest::EFailed)
-			failed = true;
-	}
-
-	for (PartDownloadRequest* req : getRequests())
-		delete req;
-
-	clearRequests();
-
-	// Update everything now
-	if (getPrimaryFile())
-	{	LDDocument::setCurrent (getPrimaryFile());
-		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 getDownloadButton();
-
-		case Abort:
-			return qobject_cast<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Abort));
-
-		case Close:
-			return qobject_cast<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Close));
-	}
-
-	return null;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-PartDownloadRequest::PartDownloadRequest (str url, str 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_NAM (new QNetworkAccessManager),
-	m_FirstUpdate (true),
-	m_Primary (primary),
-	m_FilePointer (null)
-{
-	// Make sure that we have a valid destination.
-	str dirpath = dirname (getFilePath());
-
-	QDir dir (dirpath);
-
-	if (!dir.exists())
-	{	log ("Creating %1...\n", dirpath);
-
-		if (!dir.mkpath (dirpath))
-			critical (fmt (tr ("Couldn't create the directory %1!"), dirpath));
-	}
-
-	setReply (getNAM()->get (QNetworkRequest (QUrl (url))));
-	connect (getReply(), SIGNAL (finished()), this, SLOT (downloadFinished()));
-	connect (getReply(), SIGNAL (readyRead()), this, SLOT (readyRead()));
-	connect (getReply(), SIGNAL (downloadProgress (qint64, qint64)),
-		this, SLOT (downloadProgress (qint64, qint64)));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-PartDownloadRequest::~PartDownloadRequest() {}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PartDownloadRequest::updateToTable()
-{	const int		labelcol = PartDownloader::PartLabelColumn,
-						progcol = PartDownloader::ProgressColumn;
-	QTableWidget*	table = getPrompt()->getInterface()->progress;
-	QProgressBar*	prog;
-
-	switch (getState())
-	{	case ERequesting:
-		case EDownloading:
-		{	prog = qobject_cast<QProgressBar*> (table->cellWidget (getTableRow(), progcol));
-
-			if (!prog)
-			{	prog = new QProgressBar;
-				table->setCellWidget (getTableRow(), progcol, prog);
-			}
-
-			prog->setRange (0, getBytesTotal());
-			prog->setValue (getBytesRead());
-		} break;
-
-		case EFinished:
-		case EFailed:
-		{	const str text = (getState() == 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 (getTableRow(), progcol, lb);
-		} break;
-	}
-
-	QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (getTableRow(), labelcol));
-
-	if (isFirstUpdate())
-	{	lb = new QLabel (fmt ("<b>%1</b>", getDestinaton()), table);
-		table->setCellWidget (getTableRow(), 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 (getReply()->error() != QNetworkReply::NoError)
-	{	if (isPrimary() && !getPrompt()->isAborted())
-			critical (getReply()->errorString());
-
-		setState (EFailed);
-	}
-	elif (getState() != EFailed)
-		setState (EFinished);
-
-	setBytesRead (getBytesTotal());
-	updateToTable();
-
-	if (getFilePointer())
-	{	getFilePointer()->close();
-		delete getFilePointer();
-		setFilePointer (null);
-
-		if (getState() == EFailed)
-			QFile::remove (getFilePath());
-	}
-
-	if (getState() != EFinished)
-	{	getPrompt()->checkIfFinished();
-		return;
-	}
-
-	// Try to load this file now.
-	LDDocument* f = openDocument (getFilePath(), 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->getObjects())
-	{	LDError* err = dynamic_cast<LDError*> (obj);
-
-		if (!err || err->getFileReferenced().isEmpty())
-			continue;
-
-		str dest = err->getFileReferenced();
-		getPrompt()->modifyDestination (dest);
-		getPrompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false);
-	}
-
-	if (isPrimary())
-	{	addRecentFile (getFilePath());
-		getPrompt()->setPrimaryFile (f);
-	}
-
-	getPrompt()->checkIfFinished();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PartDownloadRequest::downloadProgress (int64 recv, int64 total)
-{	setBytesRead (recv);
-	setBytesTotal (total);
-	setState (EDownloading);
-	updateToTable();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PartDownloadRequest::readyRead()
-{	if (getState() == EFailed)
-		return;
-
-	if (getFilePointer() == null)
-	{	replaceInFilePath ("\\", "/");
-
-		// We have already asked the user whether we can overwrite so we're good
-		// to go here.
-		setFilePointer (new QFile (getFilePath().toLocal8Bit()));
-
-		if (!getFilePointer()->open (QIODevice::WriteOnly))
-		{	critical (fmt (tr ("Couldn't open %1 for writing: %2"), getFilePath(), strerror (errno)));
-			setState (EFailed);
-			getReply()->abort();
-			updateToTable();
-			getPrompt()->checkIfFinished();
-			return;
-		}
-	}
-
-	getFilePointer()->write (getReply()->readAll());
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool PartDownloadRequest::isFinished() const
-{	return getState() == EFinished || getState() == EFailed;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PartDownloadRequest::abort()
-{	getReply()->abort();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (DownloadFrom, 0)
-{	PartDownloader::staticBegin();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/extprogs.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,638 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QProcess>
+#include <QTemporaryFile>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QSpinBox>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QGridLayout>
+#include "main.h"
+#include "config.h"
+#include "misc.h"
+#include "gui.h"
+#include "document.h"
+#include "widgets.h"
+#include "history.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, "");
+
+StringConfig* 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);
+
+BoolConfig* 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 checkProgPath (const extprog prog)
+{	alias path = g_extProgPaths[prog]->value;
+
+	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 str processErrorString (QProcess& proc)
+{	switch (proc.error())
+	{	case QProcess::FailedToStart:
+			return "Failed to start (check your permissions)";
+
+		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 fmt ("Timed out (30 seconds)");
+	}
+
+	return "";
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static bool mkTempFile (QTemporaryFile& tmp, str& fname)
+{	if (!tmp.open())
+		return false;
+
+	fname = tmp.fileName();
+	tmp.close();
+	return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static void writeObjects (const QList<LDObject*>& objects, File& f)
+{	for (LDObject* obj : objects)
+	{	if (obj->getType() == LDObject::Subfile)
+		{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepInline);
+
+			writeObjects (objs, f);
+
+			for (LDObject* obj : objs)
+				delete obj;
+		}
+		else
+			f.write (obj->raw() + "\r\n");
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static void writeObjects (const QList<LDObject*>& objects, str fname)
+{	// Write the input file
+	File f (fname, File::Write);
+
+	if (!f)
+	{	critical (fmt ("Couldn't open temporary file %1 for writing.\n", fname));
+		return;
+	}
+
+	writeObjects (objects, f);
+	f.close();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void writeSelection (str fname)
+{	writeObjects (selection(), fname);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void writeColorGroup (const int colnum, str fname)
+{	QList<LDObject*> objects;
+
+	for (LDObject* obj : getCurrentDocument()->getObjects())
+	{	if (obj->isColored() == false || obj->getColor() != colnum)
+			continue;
+
+		objects << obj;
+	}
+
+	writeObjects (objects, fname);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool runUtilityProcess (extprog prog, str path, str argvstr)
+{	QTemporaryFile input, output;
+	str inputname, outputname;
+	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
+
+#ifndef _WIN32
+	if (*g_extProgWine[prog])
+	{	argv.insert (0, path);
+		path = "wine";
+	}
+#endif // _WIN32
+
+	log ("cmdline: %1 %2\n", path, argv.join (" "));
+
+	// Temporary files for stdin and stdout
+	if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname))
+		return false;
+
+	QProcess proc;
+
+	// Init stdin
+	File stdinfp (inputname, File::Write);
+
+	// Begin!
+	proc.setStandardInputFile (inputname);
+	proc.start (path, argv);
+
+	// Write an enter, the utility tools all expect one
+	stdinfp.write ("\n");
+
+	// Wait while it runs
+	proc.waitForFinished();
+
+	str err = "";
+
+	if (proc.exitStatus() != QProcess::NormalExit)
+		err = processErrorString (proc);
+
+	// Check the return code
+	if (proc.exitCode() != 0)
+		err = fmt ("Program exited abnormally (return code %1).",  proc.exitCode());
+
+	if (err.length() > 0)
+	{	critical (fmt ("%1 failed: %2\n", g_extProgNames[prog], err));
+		return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static void insertOutput (str fname, bool replace, QList<int> colorsToReplace)
+{
+#ifndef RELEASE
+	QFile::copy (fname, "./debug_lastOutput");
+#endif // RELEASE
+
+	// Read the output file
+	File f (fname, File::Read);
+
+	if (!f)
+	{	critical (fmt ("Couldn't open temporary file %1 for reading.\n", fname));
+		return;
+	}
+
+	QList<LDObject*> objs = loadFileContents (&f, null);
+
+	// If we replace the objects, delete the selection now.
+	if (replace)
+		g_win->deleteSelection();
+
+	for (const int colnum : colorsToReplace)
+		g_win->deleteByColor (colnum);
+
+	// Insert the new objects
+	getCurrentDocument()->clearSelection();
+
+	for (LDObject * obj : objs)
+	{	if (!obj->isScemantic())
+		{	delete obj;
+			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;
+	str inDATName, outDATName;
+
+	// Make temp files for the input and output files
+	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
+		return;
+
+	// Compose the command-line arguments
+	str 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;
+	str inDATName, outDATName;
+
+	// Make temp files for the input and output files
+	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
+		return;
+
+	// Compose arguments
+	str 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;
+	str inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
+
+	if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) ||
+			!mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) ||
+			!mkTempFile (edgesdat, edgesDATName))
+	{	return;
+	}
+
+	str parms = join (
+	{	(ui.cb_colorize->isChecked()) ? "-c" : "",
+		(ui.cb_nocondense->isChecked()) ? "-t" : "",
+		"-s",
+		ui.dsb_prescale->value()
+	});
+
+	str argv_normal = join (
+	{	parms,
+		inDATName,
+		cutDATName,
+		outDATName
+	});
+
+	str 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;
+	str in1DATName, in2DATName, outDATName;
+
+	if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
+		return;
+
+	str argv = join (
+	{	(ui.cb_oldsweep->isChecked() ? "-s" : ""),
+		(ui.cb_reverse->isChecked() ? "-r" : ""),
+		(ui.dsb_segsplit->value() != 0 ? fmt ("-l %1", ui.dsb_segsplit->value()) : ""),
+		(ui.sb_bias->value() != 0 ? fmt ("-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;
+	str in1DATName, in2DATName, outDATName;
+
+	if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
+		return;
+
+	str 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;
+	str inName, outName;
+
+	if (!mkTempFile (in, inName) || !mkTempFile (out, outName))
+		return;
+
+	int unmatched = ui.unmatched->currentIndex();
+
+	str argv = join (
+	{	fmt ("-p %1", ui.precision->value()),
+		fmt ("-af %1", ui.flatAngle->value()),
+		fmt ("-ac %1", ui.condAngle->value()),
+		fmt ("-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/extprogs.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,638 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QProcess>
-#include <QTemporaryFile>
-#include <QDialog>
-#include <QDialogButtonBox>
-#include <QSpinBox>
-#include <QCheckBox>
-#include <QComboBox>
-#include <QGridLayout>
-#include "main.h"
-#include "config.h"
-#include "misc.h"
-#include "gui.h"
-#include "document.h"
-#include "widgets.h"
-#include "history.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, "");
-
-StringConfig* 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);
-
-BoolConfig* 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 checkProgPath (const extprog prog)
-{	alias path = g_extProgPaths[prog]->value;
-
-	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 str processErrorString (QProcess& proc)
-{	switch (proc.error())
-	{	case QProcess::FailedToStart:
-			return "Failed to start (check your permissions)";
-
-		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 fmt ("Timed out (30 seconds)");
-	}
-
-	return "";
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static bool mkTempFile (QTemporaryFile& tmp, str& fname)
-{	if (!tmp.open())
-		return false;
-
-	fname = tmp.fileName();
-	tmp.close();
-	return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static void writeObjects (const QList<LDObject*>& objects, File& f)
-{	for (LDObject* obj : objects)
-	{	if (obj->getType() == LDObject::Subfile)
-		{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepInline);
-
-			writeObjects (objs, f);
-
-			for (LDObject* obj : objs)
-				delete obj;
-		}
-		else
-			f.write (obj->raw() + "\r\n");
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static void writeObjects (const QList<LDObject*>& objects, str fname)
-{	// Write the input file
-	File f (fname, File::Write);
-
-	if (!f)
-	{	critical (fmt ("Couldn't open temporary file %1 for writing.\n", fname));
-		return;
-	}
-
-	writeObjects (objects, f);
-	f.close();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void writeSelection (str fname)
-{	writeObjects (selection(), fname);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void writeColorGroup (const int colnum, str fname)
-{	QList<LDObject*> objects;
-
-	for (LDObject* obj : getCurrentDocument()->getObjects())
-	{	if (obj->isColored() == false || obj->getColor() != colnum)
-			continue;
-
-		objects << obj;
-	}
-
-	writeObjects (objects, fname);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool runUtilityProcess (extprog prog, str path, str argvstr)
-{	QTemporaryFile input, output;
-	str inputname, outputname;
-	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
-
-#ifndef _WIN32
-	if (*g_extProgWine[prog])
-	{	argv.insert (0, path);
-		path = "wine";
-	}
-#endif // _WIN32
-
-	log ("cmdline: %1 %2\n", path, argv.join (" "));
-
-	// Temporary files for stdin and stdout
-	if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname))
-		return false;
-
-	QProcess proc;
-
-	// Init stdin
-	File stdinfp (inputname, File::Write);
-
-	// Begin!
-	proc.setStandardInputFile (inputname);
-	proc.start (path, argv);
-
-	// Write an enter, the utility tools all expect one
-	stdinfp.write ("\n");
-
-	// Wait while it runs
-	proc.waitForFinished();
-
-	str err = "";
-
-	if (proc.exitStatus() != QProcess::NormalExit)
-		err = processErrorString (proc);
-
-	// Check the return code
-	if (proc.exitCode() != 0)
-		err = fmt ("Program exited abnormally (return code %1).",  proc.exitCode());
-
-	if (err.length() > 0)
-	{	critical (fmt ("%1 failed: %2\n", g_extProgNames[prog], err));
-		return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static void insertOutput (str fname, bool replace, QList<int> colorsToReplace)
-{
-#ifndef RELEASE
-	QFile::copy (fname, "./debug_lastOutput");
-#endif // RELEASE
-
-	// Read the output file
-	File f (fname, File::Read);
-
-	if (!f)
-	{	critical (fmt ("Couldn't open temporary file %1 for reading.\n", fname));
-		return;
-	}
-
-	QList<LDObject*> objs = loadFileContents (&f, null);
-
-	// If we replace the objects, delete the selection now.
-	if (replace)
-		g_win->deleteSelection();
-
-	for (const int colnum : colorsToReplace)
-		g_win->deleteByColor (colnum);
-
-	// Insert the new objects
-	getCurrentDocument()->clearSelection();
-
-	for (LDObject * obj : objs)
-	{	if (!obj->isScemantic())
-		{	delete obj;
-			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;
-	str inDATName, outDATName;
-
-	// Make temp files for the input and output files
-	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
-		return;
-
-	// Compose the command-line arguments
-	str 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;
-	str inDATName, outDATName;
-
-	// Make temp files for the input and output files
-	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
-		return;
-
-	// Compose arguments
-	str 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;
-	str inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
-
-	if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) ||
-			!mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) ||
-			!mkTempFile (edgesdat, edgesDATName))
-	{	return;
-	}
-
-	str parms = join (
-	{	(ui.cb_colorize->isChecked()) ? "-c" : "",
-		(ui.cb_nocondense->isChecked()) ? "-t" : "",
-		"-s",
-		ui.dsb_prescale->value()
-	});
-
-	str argv_normal = join (
-	{	parms,
-		inDATName,
-		cutDATName,
-		outDATName
-	});
-
-	str 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;
-	str in1DATName, in2DATName, outDATName;
-
-	if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
-		return;
-
-	str argv = join (
-	{	(ui.cb_oldsweep->isChecked() ? "-s" : ""),
-		(ui.cb_reverse->isChecked() ? "-r" : ""),
-		(ui.dsb_segsplit->value() != 0 ? fmt ("-l %1", ui.dsb_segsplit->value()) : ""),
-		(ui.sb_bias->value() != 0 ? fmt ("-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;
-	str in1DATName, in2DATName, outDATName;
-
-	if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
-		return;
-
-	str 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;
-	str inName, outName;
-
-	if (!mkTempFile (in, inName) || !mkTempFile (out, outName))
-		return;
-
-	int unmatched = ui.unmatched->currentIndex();
-
-	str argv = join (
-	{	fmt ("-p %1", ui.precision->value()),
-		fmt ("-af %1", ui.flatAngle->value()),
-		fmt ("-ac %1", ui.condAngle->value()),
-		fmt ("-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/gldraw.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,1995 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QGLWidget>
+#include <QWheelEvent>
+#include <QMouseEvent>
+#include <QContextMenuEvent>
+#include <QInputDialog>
+#include <QToolTip>
+#include <QTimer>
+#include <GL/glu.h>
+
+#include "main.h"
+#include "config.h"
+#include "document.h"
+#include "gldraw.h"
+#include "colors.h"
+#include "gui.h"
+#include "misc.h"
+#include "history.h"
+#include "dialogs.h"
+#include "addObjectDialog.h"
+#include "messagelog.h"
+#include "primitives.h"
+#include "moc_gldraw.cpp"
+
+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 },
+};
+
+static const matrix g_circleDrawTransforms[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, "#CCCCD9");
+cfg (String, gl_maincolor, "#707078");
+cfg (Float, gl_maincolor_alpha, 1.0);
+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);
+
+// 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
+};
+
+const struct LDGLAxis
+{	const QColor col;
+	const vertex vert;
+} g_GLAxes[3] =
+{	{ QColor (255,   0,   0), vertex (10000, 0, 0) },
+	{ QColor (80,  192,   0), vertex (0, 10000, 0) },
+	{ QColor (0,   160, 192), vertex (0, 0, 10000) },
+};
+
+static bool g_glInvert = false;
+static QList<int> g_warnedColors;
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent)
+{	m_Picking = m_rangepick = false;
+	m_camera = (GL::EFixedCamera) gl_camera.value;
+	m_drawToolTip = false;
+	m_EditMode = ESelectMode;
+	m_rectdraw = false;
+	m_panning = false;
+	setFile (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)
+	{	str iconname = fmt ("camera-%1", tr (g_CameraNames[cam]).toLower());
+
+		CameraIcon* info = &m_cameraIcons[cam];
+		info->img = new QPixmap (getIcon (iconname));
+		info->cam = cam;
+	}
+
+	for (int i = 0; i < 6; ++i)
+	{	m_overlays[i].img = null;
+		m_depthValues[i] = 0.0f;
+	}
+
+	calcCameraIcons();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+GLRenderer::~GLRenderer()
+{	for (int i = 0; i < 6; ++i)
+		delete m_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()->getID();
+
+		// 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 / (256 * 256)) % 256,
+			   g = (i / 256) % 256,
+			   b = i % 256;
+
+		qglColor (QColor (r, g, b));
+		return;
+	}
+
+	if ((list == BFCFrontList || list == BFCBackList) &&
+		obj->getType() != LDObject::Line &&
+		obj->getType() != LDObject::CondLine)
+	{	if (list == GL::BFCFrontList)
+			qcol = QColor (40, 192, 0);
+		else
+			qcol = QColor (224, 0, 0);
+	}
+	else
+	{	if (obj->getColor() == maincolor)
+			qcol = getMainColor();
+		else
+		{	LDColor* col = getColor (obj->getColor());
+
+			if (col)
+				qcol = col->faceColor;
+		}
+
+		if (obj->getColor() == edgecolor)
+		{	qcol = luma (m_bgcolor) < 40 ? QColor (64, 64, 64) : Qt::black;
+			LDColor* col;
+
+			if (!gl_blackedges && obj->getParent() && (col = getColor (obj->getParent()->getColor())))
+				qcol = col->edgeColor;
+		}
+
+		if (qcol.isValid() == false)
+		{	// The color was unknown. Use main color to make the object at least
+			// not appear pitch-black.
+			if (obj->getColor() != edgecolor)
+				qcol = getMainColor();
+
+			// Warn about the unknown colors, but only once.
+			for (int i : g_warnedColors)
+				if (obj->getColor() == i)
+					return;
+
+			log ("%1: Unknown color %2!\n", __func__, obj->getColor());
+			g_warnedColors << obj->getColor();
+			return;
+		}
+	}
+
+	long r = qcol.red(),
+		 g = qcol.green(),
+		 b = qcol.blue(),
+		 a = qcol.alpha();
+
+	if (obj->topLevelParent()->isSelected())
+	{	// Brighten it up for the select list.
+		const uchar add = 51;
+
+		r = min (r + add, 255l);
+		g = min (g + add, 255l);
+		b = min (b + add, 255l);
+	}
+
+	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 (getFile() == 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 : getFile()->getObjects())
+		{	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 : getFile()->getObjects())
+		{	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;
+
+	str tmp;
+	// Create the vertex from the coordinates
+	pos3d[axisX] = tmp.sprintf ("%.3f", cx).toDouble();
+	pos3d[axisY] = tmp.sprintf ("%.3f", cy).toDouble();
+	pos3d[3 - axisX - axisY] = getDepthValue();
+	return pos3d;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+// Inverse operation for the above - convert a 3D position to a 2D screen
+// position
+// -----------------------------------------------------------------------------
+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 = getTextPen();
+	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 = m_overlays[m_camera];
+
+		if (overlay.img != null)
+		{	QPoint v0 = coordconv3_2 (m_overlays[m_camera].v0),
+					   v1 = coordconv3_2 (m_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.
+		str text = fmt (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 (getTextPen());
+		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);
+
+		// If we're drawing, draw the vertices onto the screen.
+		if (getEditMode() == EDrawMode)
+		{	int numverts = 4;
+
+			if (!m_rectdraw)
+				numverts = m_drawedVerts.size() + 1;
+
+			if (numverts > 0)
+			{	QPoint poly[4];
+				vertex polyverts[4];
+
+				if (!m_rectdraw)
+				{	uchar i = 0;
+
+					for (vertex& vert : m_drawedVerts)
+					{	poly[i] = coordconv3_2 (vert);
+						polyverts[i] = vert;
+						++i;
+					}
+
+					// Draw the cursor vertex as the last one in the list.
+					if (numverts <= 4)
+					{	poly[i] = coordconv3_2 (m_hoverpos);
+						polyverts[i] = m_hoverpos;
+					}
+					else
+					{	numverts = 4;
+					}
+				}
+				else
+				{	if (m_drawedVerts.size() > 0)
+					{	// Get vertex information from m_rectverts
+						for (int i = 0; i < numverts; ++i)
+						{	polyverts[i] = m_rectverts[i];
+							poly[i] = coordconv3_2 (polyverts[i]);
+						}
+					}
+					else
+					{	poly[0] = coordconv3_2 (m_hoverpos);
+						polyverts[0] = m_hoverpos;
+					}
+				}
+
+				// Draw the polygon-to-be
+				paint.setPen (linepen);
+				paint.setBrush (polybrush);
+				paint.drawPolygon (poly, numverts);
+
+				// Draw vertex blips
+				for (int i = 0; i < numverts; ++i)
+				{	QPoint& blip = poly[i];
+					drawBlip (paint, blip);
+
+					// Draw their coordinates
+					paint.drawText (blip.x(), blip.y() - 8, polyverts[i].stringRep (true));
+				}
+			}
+		}
+		elif (getEditMode() == 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 = 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]);
+					str label = str::number (dist0);
+					paint.setPen (textpen);
+					paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label);
+
+					if (m_drawedVerts.size() >= 2)
+					{	label = str::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] && getEditMode() != ESelectMode)
+				continue;
+
+			paint.drawPixmap (info.destRect, *info.img, info.srcRect);
+		}
+
+		str fmtstr = tr ("%1 Camera");
+
+		// Draw a label for the current camera in the bottom left corner
+		{	const int margin = 4;
+
+			str label;
+			label = fmt (fmtstr, 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
+			{	str label = fmt (fmtstr, tr (g_CameraNames[m_toolTipCamera]));
+				QToolTip::showText (m_globalpos, label);
+			}
+		}
+	}
+
+	// Message log
+	if (getMessageLog())
+	{	int y = 0;
+		const int margin = 2;
+		QColor penColor = getTextPen();
+
+		for (const MessageManager::Line& line : getMessageLog()->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);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+QColor GLRenderer::getTextPen () const
+{	return m_darkbg ? Qt::white : Qt::black;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void GLRenderer::compileAllObjects()
+{	if (!getFile())
+		return;
+
+	// Compiling all is a big job, use a busy cursor
+	setCursor (Qt::BusyCursor);
+
+	m_knownVerts.clear();
+
+	for (LDObject* obj : getFile()->getObjects())
+		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->getType() != LDObject::CondLine) ? obj->vertices() : 2;
+
+	if (g_glInvert == false)
+		for (int i = 0; i < numverts; ++i)
+			compileVertex (obj->getVertex (i));
+	else
+		for (int i = numverts - 1; i >= 0; --i)
+			compileVertex (obj->getVertex (i));
+
+	glEnd();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list)
+{	setObjectColor (obj, list);
+
+	switch (obj->getType())
+	{	case LDObject::Line:
+			compileSubObject (obj, GL_LINES);
+			break;
+
+		case LDObject::CondLine:
+
+			// 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::Triangle:
+			compileSubObject (obj, GL_TRIANGLES);
+			break;
+
+		case LDObject::Quad:
+			compileSubObject (obj, GL_QUADS);
+			break;
+
+		case LDObject::Subfile:
+		{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			QList<LDObject*> objs;
+
+			objs = ref->inlineContents (
+					   LDSubfile::DeepInline |
+					   LDSubfile::CacheInline |
+					   LDSubfile::RendererInline);
+			bool oldinvert = g_glInvert;
+
+			if (ref->getTransform().getDeterminant() < 0)
+				g_glInvert = !g_glInvert;
+
+			LDObject* prev = ref->prev();
+
+			if (prev && prev->getType() == LDObject::BFC && static_cast<LDBFC*> (prev)->type == LDBFC::InvertNext)
+				g_glInvert = !g_glInvert;
+
+		for (LDObject * obj : objs)
+			{	compileList (obj, list);
+				delete obj;
+			}
+
+			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 (getEditMode() != ECircleMode)
+	{	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 (getEditMode())
+		{	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 && getEditMode() != 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)
+{	GLint viewport[4];
+	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)
+	{	QList<LDObject*> 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();
+
+	glGetIntegerv (GL_VIEWPORT, viewport);
+
+	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];
+
+	assert (viewport[3] == m_height);
+
+	// Read pixels from the color buffer.
+	glReadPixels (x0, viewport[3] - 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);
+
+		// 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.
+			QList<LDObject*> priorsel = selection();
+			getCurrentDocument()->clearSelection();
+
+			for (LDObject* obj : priorsel)
+				compileObject (obj);
+
+			g_win->updateSelection();
+			m_drawedVerts.clear();
+		} break;
+	}
+
+	g_win->updateEditModeActions();
+	update();
+}
+
+void GLRenderer::setFile (LDDocument* const& a)
+{	m_File = a;
+
+	if (a != null)
+		initOverlaysFromObjects();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+matrix GLRenderer::getCircleDrawMatrix (double scale)
+{	matrix transform = g_circleDrawTransforms[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;
+	QList<LDObject*> objs;
+
+	switch (getEditMode())
+	{	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 = lores, divs = 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 (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, lores, lores, cmp.num))) == null)
+					{	refFile = generatePrimitive (::Ring, lores, 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)
+	{	g_win->beginAction (null);
+
+		for (LDObject* obj : objs)
+		{	getFile()->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->getVertex (i);
+	} elif (obj->getType() == LDObject::Subfile)
+	{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
+		QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepCacheInline);
+
+		for (LDObject* obj : objs)
+		{	verts << getVertices (obj);
+			delete obj;
+		}
+	}
+
+	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->m_glinit = 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->m_glinit)
+		return;
+
+for (const GL::ListType listType : g_glListTypes)
+		glDeleteLists (obj->glLists[listType], 1);
+
+	obj->m_glinit = 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, str file, int x, int y, int w, int h)
+{	QImage* img = new QImage (file);
+	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 = m_overlays[camera()];
+	delete info.img;
+	info.img = null;
+
+	updateOverlayObjects();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void GLRenderer::setDepthValue (double depth)
+{	assert (camera() < EFreeCamera);
+	m_depthValues[camera()] = depth;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+double GLRenderer::getDepthValue() const
+{	assert (camera() < EFreeCamera);
+	return m_depthValues[camera()];
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+const char* GLRenderer::getCameraName() const
+{	return g_CameraNames[camera()];
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDGLOverlay& GLRenderer::getOverlay (int newcam)
+{	return m_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 (getFile() == 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;
+
+	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);
+
+	for (;;)
+	{	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 && !filled; ++i)
+			if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white)
+				filled = true;
+
+		// Left and right edges
+		for (int i = 0; i < h && !filled; ++i)
+			if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white)
+				filled = true;
+
+		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) || getEditMode() != ESelectMode)
+		return;
+
+	pick (ev->x(), ev->y());
+
+	if (selection().isEmpty())
+		return;
+
+	g_win->beginAction (null);
+	LDObject* obj = selection().first();
+	AddObjectDialog::staticDialog (obj->getType(), obj);
+	g_win->endAction();
+	ev->accept();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam)
+{	LDOverlay* ovlobj = null;
+
+	for (LDObject * obj : getFile()->getObjects())
+	{	if (obj->getType() == LDObject::Overlay && static_cast<LDOverlay*> (obj)->getCamera() == 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 = m_overlays[cam];
+		LDOverlay* ovlobj = findOverlayObject (cam);
+
+		if (!ovlobj && meta.img)
+		{	delete meta.img;
+			meta.img = null;
+		} elif (ovlobj && (!meta.img || meta.fname != ovlobj->getFileName()))
+			setupOverlay (cam, ovlobj->getFileName(), ovlobj->getX(),
+				ovlobj->getY(), ovlobj->getWidth(), ovlobj->getHeight());
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void GLRenderer::updateOverlayObjects()
+{	for (EFixedCamera cam : g_Cameras)
+	{	if (cam == EFreeCamera)
+			continue;
+
+		LDGLOverlay& meta = m_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->getType() == LDObject::Empty)
+			{	getFile()->forgetObject (nextobj);
+				delete nextobj;
+			}
+
+			// If the overlay object was there and the overlay itself is
+			// not, remove the object.
+			getFile()->forgetObject (ovlobj);
+			delete ovlobj;
+		} 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 < getFile()->getObjectCount(); ++i)
+			{	LDObject* obj = getFile()->getObject (i);
+
+				if (obj->isScemantic())
+				{	found = true;
+					break;
+				}
+
+				if (obj->getType() == LDObject::Overlay)
+					lastOverlay = i;
+			}
+
+			if (lastOverlay != -1)
+				getFile()->insertObj (lastOverlay + 1, ovlobj);
+			else
+			{	getFile()->insertObj (i, ovlobj);
+
+				if (found)
+					getFile()->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/gldraw.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1995 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QGLWidget>
-#include <QWheelEvent>
-#include <QMouseEvent>
-#include <QContextMenuEvent>
-#include <QInputDialog>
-#include <QToolTip>
-#include <QTimer>
-#include <GL/glu.h>
-
-#include "main.h"
-#include "config.h"
-#include "document.h"
-#include "gldraw.h"
-#include "colors.h"
-#include "gui.h"
-#include "misc.h"
-#include "history.h"
-#include "dialogs.h"
-#include "addObjectDialog.h"
-#include "messagelog.h"
-#include "primitives.h"
-#include "moc_gldraw.cpp"
-
-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 },
-};
-
-static const matrix g_circleDrawTransforms[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, "#CCCCD9");
-cfg (String, gl_maincolor, "#707078");
-cfg (Float, gl_maincolor_alpha, 1.0);
-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);
-
-// 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
-};
-
-const struct LDGLAxis
-{	const QColor col;
-	const vertex vert;
-} g_GLAxes[3] =
-{	{ QColor (255,   0,   0), vertex (10000, 0, 0) },
-	{ QColor (80,  192,   0), vertex (0, 10000, 0) },
-	{ QColor (0,   160, 192), vertex (0, 0, 10000) },
-};
-
-static bool g_glInvert = false;
-static QList<int> g_warnedColors;
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent)
-{	m_Picking = m_rangepick = false;
-	m_camera = (GL::EFixedCamera) gl_camera.value;
-	m_drawToolTip = false;
-	m_EditMode = ESelectMode;
-	m_rectdraw = false;
-	m_panning = false;
-	setFile (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)
-	{	str iconname = fmt ("camera-%1", tr (g_CameraNames[cam]).toLower());
-
-		CameraIcon* info = &m_cameraIcons[cam];
-		info->img = new QPixmap (getIcon (iconname));
-		info->cam = cam;
-	}
-
-	for (int i = 0; i < 6; ++i)
-	{	m_overlays[i].img = null;
-		m_depthValues[i] = 0.0f;
-	}
-
-	calcCameraIcons();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-GLRenderer::~GLRenderer()
-{	for (int i = 0; i < 6; ++i)
-		delete m_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()->getID();
-
-		// 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 / (256 * 256)) % 256,
-			   g = (i / 256) % 256,
-			   b = i % 256;
-
-		qglColor (QColor (r, g, b));
-		return;
-	}
-
-	if ((list == BFCFrontList || list == BFCBackList) &&
-		obj->getType() != LDObject::Line &&
-		obj->getType() != LDObject::CondLine)
-	{	if (list == GL::BFCFrontList)
-			qcol = QColor (40, 192, 0);
-		else
-			qcol = QColor (224, 0, 0);
-	}
-	else
-	{	if (obj->getColor() == maincolor)
-			qcol = getMainColor();
-		else
-		{	LDColor* col = getColor (obj->getColor());
-
-			if (col)
-				qcol = col->faceColor;
-		}
-
-		if (obj->getColor() == edgecolor)
-		{	qcol = luma (m_bgcolor) < 40 ? QColor (64, 64, 64) : Qt::black;
-			LDColor* col;
-
-			if (!gl_blackedges && obj->getParent() && (col = getColor (obj->getParent()->getColor())))
-				qcol = col->edgeColor;
-		}
-
-		if (qcol.isValid() == false)
-		{	// The color was unknown. Use main color to make the object at least
-			// not appear pitch-black.
-			if (obj->getColor() != edgecolor)
-				qcol = getMainColor();
-
-			// Warn about the unknown colors, but only once.
-			for (int i : g_warnedColors)
-				if (obj->getColor() == i)
-					return;
-
-			log ("%1: Unknown color %2!\n", __func__, obj->getColor());
-			g_warnedColors << obj->getColor();
-			return;
-		}
-	}
-
-	long r = qcol.red(),
-		 g = qcol.green(),
-		 b = qcol.blue(),
-		 a = qcol.alpha();
-
-	if (obj->topLevelParent()->isSelected())
-	{	// Brighten it up for the select list.
-		const uchar add = 51;
-
-		r = min (r + add, 255l);
-		g = min (g + add, 255l);
-		b = min (b + add, 255l);
-	}
-
-	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 (getFile() == 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 : getFile()->getObjects())
-		{	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 : getFile()->getObjects())
-		{	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;
-
-	str tmp;
-	// Create the vertex from the coordinates
-	pos3d[axisX] = tmp.sprintf ("%.3f", cx).toDouble();
-	pos3d[axisY] = tmp.sprintf ("%.3f", cy).toDouble();
-	pos3d[3 - axisX - axisY] = getDepthValue();
-	return pos3d;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-// Inverse operation for the above - convert a 3D position to a 2D screen
-// position
-// -----------------------------------------------------------------------------
-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 = getTextPen();
-	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 = m_overlays[m_camera];
-
-		if (overlay.img != null)
-		{	QPoint v0 = coordconv3_2 (m_overlays[m_camera].v0),
-					   v1 = coordconv3_2 (m_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.
-		str text = fmt (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 (getTextPen());
-		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);
-
-		// If we're drawing, draw the vertices onto the screen.
-		if (getEditMode() == EDrawMode)
-		{	int numverts = 4;
-
-			if (!m_rectdraw)
-				numverts = m_drawedVerts.size() + 1;
-
-			if (numverts > 0)
-			{	QPoint poly[4];
-				vertex polyverts[4];
-
-				if (!m_rectdraw)
-				{	uchar i = 0;
-
-					for (vertex& vert : m_drawedVerts)
-					{	poly[i] = coordconv3_2 (vert);
-						polyverts[i] = vert;
-						++i;
-					}
-
-					// Draw the cursor vertex as the last one in the list.
-					if (numverts <= 4)
-					{	poly[i] = coordconv3_2 (m_hoverpos);
-						polyverts[i] = m_hoverpos;
-					}
-					else
-					{	numverts = 4;
-					}
-				}
-				else
-				{	if (m_drawedVerts.size() > 0)
-					{	// Get vertex information from m_rectverts
-						for (int i = 0; i < numverts; ++i)
-						{	polyverts[i] = m_rectverts[i];
-							poly[i] = coordconv3_2 (polyverts[i]);
-						}
-					}
-					else
-					{	poly[0] = coordconv3_2 (m_hoverpos);
-						polyverts[0] = m_hoverpos;
-					}
-				}
-
-				// Draw the polygon-to-be
-				paint.setPen (linepen);
-				paint.setBrush (polybrush);
-				paint.drawPolygon (poly, numverts);
-
-				// Draw vertex blips
-				for (int i = 0; i < numverts; ++i)
-				{	QPoint& blip = poly[i];
-					drawBlip (paint, blip);
-
-					// Draw their coordinates
-					paint.drawText (blip.x(), blip.y() - 8, polyverts[i].stringRep (true));
-				}
-			}
-		}
-		elif (getEditMode() == 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 = 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]);
-					str label = str::number (dist0);
-					paint.setPen (textpen);
-					paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label);
-
-					if (m_drawedVerts.size() >= 2)
-					{	label = str::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] && getEditMode() != ESelectMode)
-				continue;
-
-			paint.drawPixmap (info.destRect, *info.img, info.srcRect);
-		}
-
-		str fmtstr = tr ("%1 Camera");
-
-		// Draw a label for the current camera in the bottom left corner
-		{	const int margin = 4;
-
-			str label;
-			label = fmt (fmtstr, 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
-			{	str label = fmt (fmtstr, tr (g_CameraNames[m_toolTipCamera]));
-				QToolTip::showText (m_globalpos, label);
-			}
-		}
-	}
-
-	// Message log
-	if (getMessageLog())
-	{	int y = 0;
-		const int margin = 2;
-		QColor penColor = getTextPen();
-
-		for (const MessageManager::Line& line : getMessageLog()->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);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QColor GLRenderer::getTextPen () const
-{	return m_darkbg ? Qt::white : Qt::black;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void GLRenderer::compileAllObjects()
-{	if (!getFile())
-		return;
-
-	// Compiling all is a big job, use a busy cursor
-	setCursor (Qt::BusyCursor);
-
-	m_knownVerts.clear();
-
-	for (LDObject* obj : getFile()->getObjects())
-		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->getType() != LDObject::CondLine) ? obj->vertices() : 2;
-
-	if (g_glInvert == false)
-		for (int i = 0; i < numverts; ++i)
-			compileVertex (obj->getVertex (i));
-	else
-		for (int i = numverts - 1; i >= 0; --i)
-			compileVertex (obj->getVertex (i));
-
-	glEnd();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list)
-{	setObjectColor (obj, list);
-
-	switch (obj->getType())
-	{	case LDObject::Line:
-			compileSubObject (obj, GL_LINES);
-			break;
-
-		case LDObject::CondLine:
-
-			// 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::Triangle:
-			compileSubObject (obj, GL_TRIANGLES);
-			break;
-
-		case LDObject::Quad:
-			compileSubObject (obj, GL_QUADS);
-			break;
-
-		case LDObject::Subfile:
-		{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			QList<LDObject*> objs;
-
-			objs = ref->inlineContents (
-					   LDSubfile::DeepInline |
-					   LDSubfile::CacheInline |
-					   LDSubfile::RendererInline);
-			bool oldinvert = g_glInvert;
-
-			if (ref->getTransform().getDeterminant() < 0)
-				g_glInvert = !g_glInvert;
-
-			LDObject* prev = ref->prev();
-
-			if (prev && prev->getType() == LDObject::BFC && static_cast<LDBFC*> (prev)->type == LDBFC::InvertNext)
-				g_glInvert = !g_glInvert;
-
-		for (LDObject * obj : objs)
-			{	compileList (obj, list);
-				delete obj;
-			}
-
-			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 (getEditMode() != ECircleMode)
-	{	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 (getEditMode())
-		{	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 && getEditMode() != 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)
-{	GLint viewport[4];
-	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)
-	{	QList<LDObject*> 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();
-
-	glGetIntegerv (GL_VIEWPORT, viewport);
-
-	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];
-
-	assert (viewport[3] == m_height);
-
-	// Read pixels from the color buffer.
-	glReadPixels (x0, viewport[3] - 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);
-
-		// 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.
-			QList<LDObject*> priorsel = selection();
-			getCurrentDocument()->clearSelection();
-
-			for (LDObject* obj : priorsel)
-				compileObject (obj);
-
-			g_win->updateSelection();
-			m_drawedVerts.clear();
-		} break;
-	}
-
-	g_win->updateEditModeActions();
-	update();
-}
-
-void GLRenderer::setFile (LDDocument* const& a)
-{	m_File = a;
-
-	if (a != null)
-		initOverlaysFromObjects();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-matrix GLRenderer::getCircleDrawMatrix (double scale)
-{	matrix transform = g_circleDrawTransforms[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;
-	QList<LDObject*> objs;
-
-	switch (getEditMode())
-	{	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 = lores, divs = 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 (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, lores, lores, cmp.num))) == null)
-					{	refFile = generatePrimitive (::Ring, lores, 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)
-	{	g_win->beginAction (null);
-
-		for (LDObject* obj : objs)
-		{	getFile()->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->getVertex (i);
-	} elif (obj->getType() == LDObject::Subfile)
-	{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
-		QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepCacheInline);
-
-		for (LDObject* obj : objs)
-		{	verts << getVertices (obj);
-			delete obj;
-		}
-	}
-
-	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->m_glinit = 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->m_glinit)
-		return;
-
-for (const GL::ListType listType : g_glListTypes)
-		glDeleteLists (obj->glLists[listType], 1);
-
-	obj->m_glinit = 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, str file, int x, int y, int w, int h)
-{	QImage* img = new QImage (file);
-	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 = m_overlays[camera()];
-	delete info.img;
-	info.img = null;
-
-	updateOverlayObjects();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void GLRenderer::setDepthValue (double depth)
-{	assert (camera() < EFreeCamera);
-	m_depthValues[camera()] = depth;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-double GLRenderer::getDepthValue() const
-{	assert (camera() < EFreeCamera);
-	return m_depthValues[camera()];
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-const char* GLRenderer::getCameraName() const
-{	return g_CameraNames[camera()];
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDGLOverlay& GLRenderer::getOverlay (int newcam)
-{	return m_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 (getFile() == 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;
-
-	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);
-
-	for (;;)
-	{	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 && !filled; ++i)
-			if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white)
-				filled = true;
-
-		// Left and right edges
-		for (int i = 0; i < h && !filled; ++i)
-			if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white)
-				filled = true;
-
-		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) || getEditMode() != ESelectMode)
-		return;
-
-	pick (ev->x(), ev->y());
-
-	if (selection().isEmpty())
-		return;
-
-	g_win->beginAction (null);
-	LDObject* obj = selection().first();
-	AddObjectDialog::staticDialog (obj->getType(), obj);
-	g_win->endAction();
-	ev->accept();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam)
-{	LDOverlay* ovlobj = null;
-
-	for (LDObject * obj : getFile()->getObjects())
-	{	if (obj->getType() == LDObject::Overlay && static_cast<LDOverlay*> (obj)->getCamera() == 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 = m_overlays[cam];
-		LDOverlay* ovlobj = findOverlayObject (cam);
-
-		if (!ovlobj && meta.img)
-		{	delete meta.img;
-			meta.img = null;
-		} elif (ovlobj && (!meta.img || meta.fname != ovlobj->getFileName()))
-			setupOverlay (cam, ovlobj->getFileName(), ovlobj->getX(),
-				ovlobj->getY(), ovlobj->getWidth(), ovlobj->getHeight());
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void GLRenderer::updateOverlayObjects()
-{	for (EFixedCamera cam : g_Cameras)
-	{	if (cam == EFreeCamera)
-			continue;
-
-		LDGLOverlay& meta = m_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->getType() == LDObject::Empty)
-			{	getFile()->forgetObject (nextobj);
-				delete nextobj;
-			}
-
-			// If the overlay object was there and the overlay itself is
-			// not, remove the object.
-			getFile()->forgetObject (ovlobj);
-			delete ovlobj;
-		} 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 < getFile()->getObjectCount(); ++i)
-			{	LDObject* obj = getFile()->getObject (i);
-
-				if (obj->isScemantic())
-				{	found = true;
-					break;
-				}
-
-				if (obj->getType() == LDObject::Overlay)
-					lastOverlay = i;
-			}
-
-			if (lastOverlay != -1)
-				getFile()->insertObj (lastOverlay + 1, ovlobj);
-			else
-			{	getFile()->insertObj (i, ovlobj);
-
-				if (found)
-					getFile()->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/gui.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,945 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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 "main.h"
+#include "gldraw.h"
+#include "gui.h"
+#include "document.h"
+#include "config.h"
+#include "misc.h"
+#include "colors.h"
+#include "history.h"
+#include "widgets.h"
+#include "addObjectDialog.h"
+#include "messagelog.h"
+#include "config.h"
+#include "ui_ldforge.h"
+#include "moc_gui.cpp"
+
+static bool g_isSelectionLocked = false;
+
+cfg (Bool, lv_colorize, true);
+cfg (String, gui_colortoolbar, "16:24:|:1:2:4:14:0:15:|:33:34:36:46");
+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);
+
+#define act(N) extern_cfg (KeySequence, key_##N);
+#include "actions.h"
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+ForgeWindow::ForgeWindow()
+{	g_win = this;
+	m_renderer = new GLRenderer;
+
+	ui = new Ui_LDForgeUI;
+	ui->setupUi (this);
+
+	// 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 (ui->fileList, SIGNAL (currentItemChanged (QListWidgetItem*, QListWidgetItem*)), 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);
+
+	// Init primitive loader task stuff
+	m_primLoaderBar = new QProgressBar;
+	m_primLoaderWidget = new QWidget;
+	QHBoxLayout* primLoaderLayout = new QHBoxLayout (m_primLoaderWidget);
+	primLoaderLayout->addWidget (new QLabel ("Loading primitives:"));
+	primLoaderLayout->addWidget (m_primLoaderBar);
+	statusBar()->addPermanentWidget (m_primLoaderWidget);
+	m_primLoaderWidget->hide();
+
+	// Make certain actions checkable
+	ui->actionAxes->setChecked (gl_axes);
+	ui->actionWireframe->setChecked (gl_wireframe);
+	ui->actionBFCView->setChecked (gl_colorbfc);
+	updateGridToolBar();
+	updateEditModeActions();
+	updateRecentFilesMenu();
+	updateToolBars();
+	updateTitle();
+
+	setMinimumSize (300, 200);
+
+	connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup()));
+
+	// Connect all actions and set shortcuts
+#define act(N) \
+	connect (ui->action##N, SIGNAL (triggered()), this, SLOT (slot_action())); \
+	ui->action##N->setShortcut (key_##N);
+#include "actions.h"
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::slot_action()
+{	// Find out which action triggered this
+#define act(N) if (sender() == ui->action##N) invokeAction (ui->action##N, &actiondef_##N);
+#include "actions.h"
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::invokeAction (QAction* act, void (*func)())
+{
+#ifdef DEBUG
+	log ("Action %1 triggered", act->iconText());
+#endif
+
+	beginAction (act);
+	(*func) ();
+	endAction();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::slot_lastSecondCleanup()
+{	delete m_renderer;
+	delete ui;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::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)
+	{	str 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 (str colorname : gui_colortoolbar.value.split (":"))
+	{	if (colorname == "|")
+			colors << LDQuickColor::getSeparator();
+		else
+		{	LDColor* col = getColor (colorname.toLong());
+
+			if (col != null)
+				colors << LDQuickColor (col, null);
+		}
+	}
+
+	return colors;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::updateToolBars()
+{	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.getColor(), 22));
+			colorButton->setIconSize (QSize (22, 22));
+			colorButton->setToolTip (entry.getColor()->name);
+
+			connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor()));
+			ui->colorToolbar->addWidget (colorButton);
+			m_colorButtons << colorButton;
+
+			entry.setToolButton (colorButton);
+		}
+	}
+
+	updateGridToolBar();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::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 ForgeWindow::updateTitle()
+{	str title = fmt (APPNAME " %1", fullVersionString());
+
+	// Append our current file if we have one
+	if (getCurrentDocument())
+	{	if (getCurrentDocument()->getName().length() > 0)
+			title += fmt (": %1", basename (getCurrentDocument()->getName()));
+		else
+			title += fmt (": <anonymous>");
+
+		if (getCurrentDocument()->getObjectCount() > 0 &&
+				getCurrentDocument()->getObject (0)->getType() == LDObject::Comment)
+		{	// Append title
+			LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0));
+			title += fmt (": %1", comm->text);
+		}
+
+		if (getCurrentDocument()->getHistory()->getPosition() != getCurrentDocument()->getSavePosition())
+			title += '*';
+	}
+
+#ifdef DEBUG
+	title += " [debug build]";
+#elif BUILD_ID != BUILD_RELEASE
+	title += " [release build]";
+#endif // DEBUG
+
+	setWindowTitle (title);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+int ForgeWindow::deleteSelection()
+{	if (selection().isEmpty())
+		return 0;
+
+	QList<LDObject*> selCopy = selection();
+
+	// Delete the objects that were being selected
+	for (LDObject* obj : selCopy)
+		delete obj;
+
+	refresh();
+	return selCopy.size();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::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()->getObjects())
+	{	str descr;
+
+		switch (obj->getType())
+		{	case LDObject::Comment:
+			{	descr = static_cast<LDComment*> (obj)->text;
+
+				// Remove leading whitespace
+				while (descr[0] == ' ')
+					descr.remove (0, 1);
+			} break;
+
+			case LDObject::Empty:
+				break; // leave it empty
+
+			case LDObject::Line:
+			case LDObject::Triangle:
+			case LDObject::Quad:
+			case LDObject::CondLine:
+			{	for (int i = 0; i < obj->vertices(); ++i)
+				{	if (i != 0)
+						descr += ", ";
+
+					descr += obj->getVertex (i).stringRep (true);
+				}
+			} break;
+
+			case LDObject::Error:
+			{	descr = fmt ("ERROR: %1", obj->raw());
+			} break;
+
+			case LDObject::Vertex:
+			{	descr = static_cast<LDVertex*> (obj)->pos.stringRep (true);
+			} break;
+
+			case LDObject::Subfile:
+			{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
+
+				descr = fmt ("%1 %2, (", ref->getFileInfo()->getName(), ref->getPosition().stringRep (true));
+
+				for (int i = 0; i < 9; ++i)
+					descr += fmt ("%1%2", ref->getTransform()[i], (i != 8) ? " " : "");
+
+				descr += ')';
+			} break;
+
+			case LDObject::BFC:
+			{	descr = LDBFC::statements[static_cast<LDBFC*> (obj)->type];
+			} break;
+
+			case LDObject::Overlay:
+			{	LDOverlay* ovl = static_cast<LDOverlay*> (obj);
+				descr = fmt ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->getCamera()],
+					basename (ovl->getFileName()), ovl->getX(), ovl->getY(),
+					ovl->getWidth(), ovl->getHeight());
+			}
+			break;
+
+			default:
+			{	descr = obj->getTypeName();
+			} break;
+		}
+
+		QListWidgetItem* item = new QListWidgetItem (descr);
+		item->setIcon (getIcon (obj->getTypeName()));
+		
+		// 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->getType() == LDObject::Error)
+		{	item->setBackground (QColor ("#AA0000"));
+			item->setForeground (QColor ("#FFAA00"));
+		}
+		elif (lv_colorize && obj->isColored() && obj->getColor() != maincolor && obj->getColor() != edgecolor)
+		{	// If the object isn't in the main or edge color, draw this
+			// list entry in said color.
+			LDColor* col = getColor (obj->getColor());
+
+			if (col)
+				item->setForeground (col->faceColor);
+		}
+
+		obj->qObjListEntry = item;
+		ui->objectList->insertItem (ui->objectList->count(), item);
+	}
+
+	g_isSelectionLocked = false;
+	updateSelection();
+	scrollToSelection();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::scrollToSelection()
+{	if (selection().isEmpty())
+		return;
+
+	LDObject* obj = selection().last();
+	ui->objectList->scrollToItem (obj->qObjListEntry);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::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;
+
+	QList<LDObject*> priorSelection = selection();
+
+	// Get the objects from the object list selection
+	getCurrentDocument()->clearSelection();
+	const QList<QListWidgetItem*> items = ui->objectList->selectedItems();
+
+	for (LDObject* obj : getCurrentDocument()->getObjects())
+	{	for (QListWidgetItem* item : items)
+		{	if (item == obj->qObjListEntry)
+			{	obj->select();
+				break;
+			}
+		}
+	}
+
+	// Update the GL renderer
+	QList<LDObject*> compound = priorSelection + selection();
+	removeDuplicates (compound);
+
+	for (LDObject* obj : compound)
+		m_renderer->compileObject (obj);
+
+	m_renderer->update();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::slot_recentFile()
+{	QAction* qAct = static_cast<QAction*> (sender());
+	openMainFile (qAct->text());
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::slot_quickColor()
+{	beginAction (null);
+	QToolButton* button = static_cast<QToolButton*> (sender());
+	LDColor* col = null;
+
+	for (const LDQuickColor & entry : m_quickColors)
+	{	if (entry.getToolButton() == button)
+		{	col = entry.getColor();
+			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);
+	}
+
+	refresh();
+	endAction();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+int ForgeWindow::getInsertionPoint()
+{	// If we have a selection, put the item after it.
+	if (!selection().isEmpty())
+		return selection().last()->getIndex() + 1;
+
+	// Otherwise place the object at the end.
+	return getCurrentDocument()->getObjectCount();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::doFullRefresh()
+{	buildObjList();
+	m_renderer->hardRefresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::refresh()
+{	buildObjList();
+	m_renderer->update();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::updateSelection()
+{	g_isSelectionLocked = true;
+
+	for (LDObject* obj : getCurrentDocument()->getObjects())
+		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 ForgeWindow::getSelectedColor()
+{	int result = -1;
+
+	for (LDObject* obj : selection())
+	{	if (obj->isColored() == false)
+			continue; // doesn't use color
+
+		if (result != -1 && obj->getColor() != result)
+			return -1; // No consensus in object color
+
+		if (result == -1)
+			result = obj->getColor();
+	}
+
+	return result;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDObject::Type ForgeWindow::getUniformSelectedType()
+{	LDObject::Type result = LDObject::Unidentified;
+
+	for (LDObject * obj : selection())
+	{	if (result != LDObject::Unidentified && obj->getColor() != result)
+			return LDObject::Unidentified;
+
+		if (result == LDObject::Unidentified)
+			result = obj->getType();
+	}
+
+	return result;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::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 ForgeWindow::spawnContextMenu (const QPoint pos)
+{	const bool single = (selection().size() == 1);
+	LDObject* singleObj = (single) ? selection()[0] : null;
+
+	QMenu* contextMenu = new QMenu;
+
+	if (single && singleObj->getType() != LDObject::Empty)
+	{	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 (R()->camera() != GL::EFreeCamera)
+	{	contextMenu->addSeparator();
+		contextMenu->addAction (ui->actionSetDrawDepth);
+	}
+
+	contextMenu->exec (pos);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::deleteObjects (QList<LDObject*> objs)
+{	for (LDObject * obj : objs)
+	{	getCurrentDocument()->forgetObject (obj);
+		delete obj;
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::deleteByColor (const int colnum)
+{	QList<LDObject*> objs;
+
+	for (LDObject* obj : getCurrentDocument()->getObjects())
+	{	if (!obj->isColored() || obj->getColor() != colnum)
+			continue;
+
+		objs << obj;
+	}
+
+	deleteObjects (objs);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::updateEditModeActions()
+{	const EditMode mode = R()->getEditMode();
+	ui->actionModeSelect->setChecked (mode == ESelectMode);
+	ui->actionModeDraw->setChecked (mode == EDrawMode);
+	ui->actionModeCircle->setChecked (mode == ECircleMode);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::slot_editObject (QListWidgetItem* listitem)
+{	LDObject* obj = null;
+
+	for (LDObject* it : getCurrentDocument()->getObjects())
+	{	if (it->qObjListEntry == listitem)
+		{	obj = it;
+			break;
+		}
+	}
+
+	AddObjectDialog::staticDialog (obj->getType(), obj);
+}
+
+#if 0
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::primitiveLoaderStart (int max)
+{	m_primLoaderWidget->show();
+	m_primLoaderBar->setRange (0, max);
+	m_primLoaderBar->setValue (0);
+	m_primLoaderBar->setFormat ("%p%");
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::primitiveLoaderUpdate (int prog)
+{	m_primLoaderBar->setValue (prog);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::primitiveLoaderEnd()
+{	QTimer* hidetimer = new QTimer;
+	connect (hidetimer, SIGNAL (timeout()), m_primLoaderWidget, SLOT (hide()));
+	hidetimer->setSingleShot (true);
+	hidetimer->start (1500);
+	m_primLoaderBar->setFormat (tr ("Done"));
+	log (tr ("Primitives scanned: %1 primitives listed"), m_primLoaderBar->value());
+}
+#endif
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void ForgeWindow::save (LDDocument* f, bool saveAs)
+{	str path = f->getName();
+
+	if (saveAs || path.isEmpty())
+	{	path = QFileDialog::getSaveFileName (g_win, tr ("Save As"),
+			(f->getName().isEmpty()) ? f->getName() : f->getDefaultName(),
+			tr ("LDraw files (*.dat *.ldr)"));
+
+		if (path.isEmpty())
+		{	// User didn't give a file name, abort.
+			return;
+		}
+	}
+
+	if (f->save (path))
+	{	f->setName (path);
+
+		if (f == getCurrentDocument())
+			g_win->updateTitle();
+
+		log ("Saved to %1.", path);
+
+		// Add it to recent files
+		addRecentFile (path);
+	}
+	else
+	{	str message = fmt (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)
+			save (f, true); // yay recursion!
+	}
+}
+
+void ForgeWindow::addMessage (str msg)
+{	m_msglog->addLine (msg);
+}
+
+// ============================================================================
+void ObjectList::contextMenuEvent (QContextMenuEvent* ev)
+{	g_win->spawnContextMenu (ev->globalPos());
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+QPixmap getIcon (str iconName)
+{	return (QPixmap (fmt (":/icons/%1.png", iconName)));
+}
+
+// =============================================================================
+bool confirm (str msg)
+{	return confirm (ForgeWindow::tr ("Confirm"), msg);
+}
+
+bool confirm (str title, str msg)
+{	return QMessageBox::question (g_win, title, msg,
+		(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes;
+}
+
+// =============================================================================
+void critical (str msg)
+{	QMessageBox::critical (g_win, ForgeWindow::tr ("Error"), msg,
+		(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.value;
+		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()->getObjects())
+	{	if (!obj->isColored())
+			continue;
+
+		if (counts.find (obj->getColor()) == counts.end())
+			counts[obj->getColor()] = 1;
+		else
+			counts[obj->getColor()]++;
+	}
+
+	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, fmt ("[%1] %2 (%3 object%4)",
+			pair.first, col->name, pair.second, plural (pair.second)));
+		box->setItemData (row, pair.first);
+
+		++row;
+	}
+}
+
+void ForgeWindow::setStatusBarText (str text)
+{	statusBar()->showMessage (text);
+}
+
+Ui_LDForgeUI* ForgeWindow::interface() const
+{	return ui;
+}
+
+#define act(N) QAction* ForgeWindow::action##N() { return ui->action##N; }
+#include "actions.h"
+
+void ForgeWindow::updateDocumentList()
+{	ui->fileList->clear();
+
+	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 a pointer to it in
+		// the file, so we can find files by the list item.
+		ui->fileList->addItem ("");
+		QListWidgetItem* item = ui->fileList->item (ui->fileList->count() - 1);
+		f->setListItem (item);
+
+		updateDocumentListItem (f);
+	}
+}
+
+void ForgeWindow::updateDocumentListItem (LDDocument* f)
+{	if (f->getListItem() == null)
+	{	// We don't have a list item for this file, so the list either doesn't
+		// exist yet or is out of date. Build the list now.
+		updateDocumentList();
+		return;
+	}
+
+	// If this is the current file, it also needs to be the selected item on
+	// the list.
+	if (f == getCurrentDocument())
+		ui->fileList->setCurrentItem (f->getListItem());
+
+	// If we list implicit files, draw them with a shade of gray to make them
+	// distinct.
+	if (f->isImplicit())
+		f->getListItem()->setForeground (QColor (96, 96, 96));
+
+	f->getListItem()->setText (f->getShortName());
+
+	// If the document has unsaved changes, draw a little icon next to it to mark that.
+	f->getListItem()->setIcon (f->hasUnsavedChanges() ? getIcon ("file-save") : QIcon());
+}
+
+void ForgeWindow::beginAction (QAction* act)
+{	// Open the history so we can record the edits done during this action.
+	if (act == ui->actionUndo || act == ui->actionRedo || act == ui->actionOpen)
+		getCurrentDocument()->getHistory()->setIgnoring (true);
+}
+
+void ForgeWindow::endAction()
+{	// Close 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());
+}
+
+// =============================================================================
+// A file is selected from the list of files on the left of the screen. Find out
+// which file was picked and change to it.
+void ForgeWindow::changeCurrentFile()
+{	LDDocument* f = null;
+	QListWidgetItem* item = ui->fileList->currentItem();
+
+	// Find the file pointer of the item that was selected.
+	for (LDDocument* it : g_loadedFiles)
+	{	if (it->getListItem() == item)
+		{	f = it;
+			break;
+		}
+	}
+
+	// If we picked the same file we're currently on, we don't need to do
+	// anything.
+	if (!f || f == getCurrentDocument())
+		return;
+
+	LDDocument::setCurrent (f);
+}
+
+void ForgeWindow::refreshObjectList()
+{
+#if 0
+	ui->objectList->clear();
+	LDDocument* f = getCurrentDocument();
+
+for (LDObject * obj : *f)
+		ui->objectList->addItem (obj->qObjListEntry);
+
+#endif
+
+	buildObjList();
+}
+
+void ForgeWindow::updateActions()
+{	History* his = getCurrentDocument()->getHistory();
+	int pos = his->getPosition();
+	ui->actionUndo->setEnabled (pos != -1);
+	ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1);
+	ui->actionAxes->setChecked (gl_axes);
+	ui->actionBFCView->setChecked (gl_colorbfc);
+}
+
+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 getColor() == null;
+}
--- a/src/gui.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,942 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#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 "main.h"
-#include "gldraw.h"
-#include "gui.h"
-#include "document.h"
-#include "config.h"
-#include "misc.h"
-#include "colors.h"
-#include "history.h"
-#include "widgets.h"
-#include "addObjectDialog.h"
-#include "messagelog.h"
-#include "config.h"
-#include "ui_ldforge.h"
-#include "moc_gui.cpp"
-
-static bool g_isSelectionLocked = false;
-
-cfg (Bool, lv_colorize, true);
-cfg (String, gui_colortoolbar, "16:24:|:1:2:4:14:0:15:|:33:34:36:46");
-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);
-
-#define act(N) extern_cfg (KeySequence, key_##N);
-#include "actions.h"
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-ForgeWindow::ForgeWindow()
-{	g_win = this;
-	m_renderer = new GLRenderer;
-
-	ui = new Ui_LDForgeUI;
-	ui->setupUi (this);
-
-	// 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 (ui->fileList, SIGNAL (currentItemChanged (QListWidgetItem*, QListWidgetItem*)), 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);
-
-	// Init primitive loader task stuff
-	m_primLoaderBar = new QProgressBar;
-	m_primLoaderWidget = new QWidget;
-	QHBoxLayout* primLoaderLayout = new QHBoxLayout (m_primLoaderWidget);
-	primLoaderLayout->addWidget (new QLabel ("Loading primitives:"));
-	primLoaderLayout->addWidget (m_primLoaderBar);
-	statusBar()->addPermanentWidget (m_primLoaderWidget);
-	m_primLoaderWidget->hide();
-
-	// Make certain actions checkable
-	ui->actionAxes->setChecked (gl_axes);
-	ui->actionWireframe->setChecked (gl_wireframe);
-	ui->actionBFCView->setChecked (gl_colorbfc);
-	updateGridToolBar();
-	updateEditModeActions();
-	updateRecentFilesMenu();
-	updateToolBars();
-	updateTitle();
-
-	setMinimumSize (300, 200);
-
-	connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup()));
-
-	// Connect all actions and set shortcuts
-#define act(N) \
-	connect (ui->action##N, SIGNAL (triggered()), this, SLOT (slot_action())); \
-	ui->action##N->setShortcut (key_##N);
-#include "actions.h"
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::slot_action()
-{	// Find out which action triggered this
-#define act(N) if (sender() == ui->action##N) invokeAction (ui->action##N, &actiondef_##N);
-#include "actions.h"
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::invokeAction (QAction* act, void (*func)())
-{
-#ifdef DEBUG
-	log ("Action %1 triggered", act->iconText());
-#endif
-
-	beginAction (act);
-	(*func) ();
-	endAction();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::slot_lastSecondCleanup()
-{	delete m_renderer;
-	delete ui;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::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)
-	{	str 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 (str colorname : gui_colortoolbar.value.split (":"))
-	{	if (colorname == "|")
-			colors << LDQuickColor::getSeparator();
-		else
-		{	LDColor* col = getColor (colorname.toLong());
-
-			if (col != null)
-				colors << LDQuickColor (col, null);
-		}
-	}
-
-	return colors;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::updateToolBars()
-{	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.getColor(), 22));
-			colorButton->setIconSize (QSize (22, 22));
-			colorButton->setToolTip (entry.getColor()->name);
-
-			connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor()));
-			ui->colorToolbar->addWidget (colorButton);
-			m_colorButtons << colorButton;
-
-			entry.setToolButton (colorButton);
-		}
-	}
-
-	updateGridToolBar();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::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 ForgeWindow::updateTitle()
-{	str title = fmt (APPNAME " %1", fullVersionString());
-
-	// Append our current file if we have one
-	if (getCurrentDocument())
-	{	if (getCurrentDocument()->getName().length() > 0)
-			title += fmt (": %1", basename (getCurrentDocument()->getName()));
-		else
-			title += fmt (": <anonymous>");
-
-		if (getCurrentDocument()->getObjectCount() > 0 &&
-				getCurrentDocument()->getObject (0)->getType() == LDObject::Comment)
-		{	// Append title
-			LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0));
-			title += fmt (": %1", comm->text);
-		}
-
-		if (getCurrentDocument()->getHistory()->getPosition() != getCurrentDocument()->getSavePosition())
-			title += '*';
-	}
-
-#ifdef DEBUG
-	title += " [debug build]";
-#elif BUILD_ID != BUILD_RELEASE
-	title += " [release build]";
-#endif // DEBUG
-
-	setWindowTitle (title);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-int ForgeWindow::deleteSelection()
-{	if (selection().isEmpty())
-		return 0;
-
-	QList<LDObject*> selCopy = selection();
-
-	// Delete the objects that were being selected
-	for (LDObject* obj : selCopy)
-		delete obj;
-
-	refresh();
-	return selCopy.size();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::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()->getObjects())
-	{	str descr;
-
-		switch (obj->getType())
-		{	case LDObject::Comment:
-			{	descr = static_cast<LDComment*> (obj)->text;
-
-				// Remove leading whitespace
-				while (descr[0] == ' ')
-					descr.remove (0, 1);
-			} break;
-
-			case LDObject::Empty:
-				break; // leave it empty
-
-			case LDObject::Line:
-			case LDObject::Triangle:
-			case LDObject::Quad:
-			case LDObject::CondLine:
-			{	for (int i = 0; i < obj->vertices(); ++i)
-				{	if (i != 0)
-						descr += ", ";
-
-					descr += obj->getVertex (i).stringRep (true);
-				}
-			} break;
-
-			case LDObject::Error:
-			{	descr = fmt ("ERROR: %1", obj->raw());
-			} break;
-
-			case LDObject::Vertex:
-			{	descr = static_cast<LDVertex*> (obj)->pos.stringRep (true);
-			} break;
-
-			case LDObject::Subfile:
-			{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
-
-				descr = fmt ("%1 %2, (", ref->getFileInfo()->getName(), ref->getPosition().stringRep (true));
-
-				for (int i = 0; i < 9; ++i)
-					descr += fmt ("%1%2", ref->getTransform()[i], (i != 8) ? " " : "");
-
-				descr += ')';
-			} break;
-
-			case LDObject::BFC:
-			{	descr = LDBFC::statements[static_cast<LDBFC*> (obj)->type];
-			} break;
-
-			case LDObject::Overlay:
-			{	LDOverlay* ovl = static_cast<LDOverlay*> (obj);
-				descr = fmt ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->getCamera()],
-					basename (ovl->getFileName()), ovl->getX(), ovl->getY(),
-					ovl->getWidth(), ovl->getHeight());
-			}
-			break;
-
-			default:
-			{	descr = obj->getTypeName();
-			} break;
-		}
-
-		QListWidgetItem* item = new QListWidgetItem (descr);
-		item->setIcon (getIcon (obj->getTypeName()));
-		
-		// 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->getType() == LDObject::Error)
-		{	item->setBackground (QColor ("#AA0000"));
-			item->setForeground (QColor ("#FFAA00"));
-		}
-		elif (lv_colorize && obj->isColored() && obj->getColor() != maincolor && obj->getColor() != edgecolor)
-		{	// If the object isn't in the main or edge color, draw this
-			// list entry in said color.
-			LDColor* col = getColor (obj->getColor());
-
-			if (col)
-				item->setForeground (col->faceColor);
-		}
-
-		obj->qObjListEntry = item;
-		ui->objectList->insertItem (ui->objectList->count(), item);
-	}
-
-	g_isSelectionLocked = false;
-	updateSelection();
-	scrollToSelection();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::scrollToSelection()
-{	if (selection().isEmpty())
-		return;
-
-	LDObject* obj = selection().last();
-	ui->objectList->scrollToItem (obj->qObjListEntry);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::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;
-
-	QList<LDObject*> priorSelection = selection();
-
-	// Get the objects from the object list selection
-	getCurrentDocument()->clearSelection();
-	const QList<QListWidgetItem*> items = ui->objectList->selectedItems();
-
-	for (LDObject* obj : getCurrentDocument()->getObjects())
-	{	for (QListWidgetItem* item : items)
-		{	if (item == obj->qObjListEntry)
-			{	obj->select();
-				break;
-			}
-		}
-	}
-
-	// Update the GL renderer
-	QList<LDObject*> compound = priorSelection + selection();
-	removeDuplicates (compound);
-
-	for (LDObject* obj : compound)
-		m_renderer->compileObject (obj);
-
-	m_renderer->update();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::slot_recentFile()
-{	QAction* qAct = static_cast<QAction*> (sender());
-	openMainFile (qAct->text());
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::slot_quickColor()
-{	beginAction (null);
-	QToolButton* button = static_cast<QToolButton*> (sender());
-	LDColor* col = null;
-
-	for (const LDQuickColor & entry : m_quickColors)
-	{	if (entry.getToolButton() == button)
-		{	col = entry.getColor();
-			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);
-	}
-
-	refresh();
-	endAction();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-int ForgeWindow::getInsertionPoint()
-{	// If we have a selection, put the item after it.
-	if (!selection().isEmpty())
-		return selection().last()->getIndex() + 1;
-
-	// Otherwise place the object at the end.
-	return getCurrentDocument()->getObjectCount();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::doFullRefresh()
-{	buildObjList();
-	m_renderer->hardRefresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::refresh()
-{	buildObjList();
-	m_renderer->update();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::updateSelection()
-{	g_isSelectionLocked = true;
-
-	for (LDObject* obj : getCurrentDocument()->getObjects())
-		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 ForgeWindow::getSelectedColor()
-{	int result = -1;
-
-	for (LDObject* obj : selection())
-	{	if (obj->isColored() == false)
-			continue; // doesn't use color
-
-		if (result != -1 && obj->getColor() != result)
-			return -1; // No consensus in object color
-
-		if (result == -1)
-			result = obj->getColor();
-	}
-
-	return result;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObject::Type ForgeWindow::getUniformSelectedType()
-{	LDObject::Type result = LDObject::Unidentified;
-
-	for (LDObject * obj : selection())
-	{	if (result != LDObject::Unidentified && obj->getColor() != result)
-			return LDObject::Unidentified;
-
-		if (result == LDObject::Unidentified)
-			result = obj->getType();
-	}
-
-	return result;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::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 ForgeWindow::spawnContextMenu (const QPoint pos)
-{	const bool single = (selection().size() == 1);
-	LDObject* singleObj = (single) ? selection()[0] : null;
-
-	QMenu* contextMenu = new QMenu;
-
-	if (single && singleObj->getType() != LDObject::Empty)
-	{	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 (R()->camera() != GL::EFreeCamera)
-	{	contextMenu->addSeparator();
-		contextMenu->addAction (ui->actionSetDrawDepth);
-	}
-
-	contextMenu->exec (pos);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::deleteObjects (QList<LDObject*> objs)
-{	for (LDObject * obj : objs)
-	{	getCurrentDocument()->forgetObject (obj);
-		delete obj;
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::deleteByColor (const int colnum)
-{	QList<LDObject*> objs;
-
-	for (LDObject* obj : getCurrentDocument()->getObjects())
-	{	if (!obj->isColored() || obj->getColor() != colnum)
-			continue;
-
-		objs << obj;
-	}
-
-	deleteObjects (objs);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::updateEditModeActions()
-{	const EditMode mode = R()->getEditMode();
-	ui->actionModeSelect->setChecked (mode == ESelectMode);
-	ui->actionModeDraw->setChecked (mode == EDrawMode);
-	ui->actionModeCircle->setChecked (mode == ECircleMode);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::slot_editObject (QListWidgetItem* listitem)
-{	LDObject* obj = null;
-
-	for (LDObject* it : getCurrentDocument()->getObjects())
-	{	if (it->qObjListEntry == listitem)
-		{	obj = it;
-			break;
-		}
-	}
-
-	AddObjectDialog::staticDialog (obj->getType(), obj);
-}
-
-#if 0
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::primitiveLoaderStart (int max)
-{	m_primLoaderWidget->show();
-	m_primLoaderBar->setRange (0, max);
-	m_primLoaderBar->setValue (0);
-	m_primLoaderBar->setFormat ("%p%");
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::primitiveLoaderUpdate (int prog)
-{	m_primLoaderBar->setValue (prog);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::primitiveLoaderEnd()
-{	QTimer* hidetimer = new QTimer;
-	connect (hidetimer, SIGNAL (timeout()), m_primLoaderWidget, SLOT (hide()));
-	hidetimer->setSingleShot (true);
-	hidetimer->start (1500);
-	m_primLoaderBar->setFormat (tr ("Done"));
-	log (tr ("Primitives scanned: %1 primitives listed"), m_primLoaderBar->value());
-}
-#endif
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void ForgeWindow::save (LDDocument* f, bool saveAs)
-{	str path = f->getName();
-
-	if (saveAs || path.isEmpty())
-	{	path = QFileDialog::getSaveFileName (g_win, tr ("Save As"),
-			(f->getName().isEmpty()) ? f->getName() : f->getDefaultName(),
-			tr ("LDraw files (*.dat *.ldr)"));
-
-		if (path.isEmpty())
-		{	// User didn't give a file name, abort.
-			return;
-		}
-	}
-
-	if (f->save (path))
-	{	f->setName (path);
-
-		if (f == getCurrentDocument())
-			g_win->updateTitle();
-
-		log ("Saved to %1.", path);
-
-		// Add it to recent files
-		addRecentFile (path);
-	}
-	else
-	{	str message = fmt (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)
-			save (f, true); // yay recursion!
-	}
-}
-
-void ForgeWindow::addMessage (str msg)
-{	m_msglog->addLine (msg);
-}
-
-// ============================================================================
-void ObjectList::contextMenuEvent (QContextMenuEvent* ev)
-{	g_win->spawnContextMenu (ev->globalPos());
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QPixmap getIcon (str iconName)
-{	return (QPixmap (fmt (":/icons/%1.png", iconName)));
-}
-
-// =============================================================================
-bool confirm (str msg)
-{	return confirm (ForgeWindow::tr ("Confirm"), msg);
-}
-
-bool confirm (str title, str msg)
-{	return QMessageBox::question (g_win, title, msg,
-		(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes;
-}
-
-// =============================================================================
-void critical (str msg)
-{	QMessageBox::critical (g_win, ForgeWindow::tr ("Error"), msg,
-		(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.value;
-		col.setAlphaF (gl_maincolor_alpha);
-	}
-
-	// Paint the icon
-	paint.fillRect (QRect (0, 0, size, size), colinfo->edgeColor);
-	paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8));
-	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()->getObjects())
-	{	if (!obj->isColored())
-			continue;
-
-		if (counts.find (obj->getColor()) == counts.end())
-			counts[obj->getColor()] = 1;
-		else
-			counts[obj->getColor()]++;
-	}
-
-	box->clear();
-	int row = 0;
-
-	for (const std::pair<int, int>& pair : counts)
-	{	LDColor* col = getColor (pair.first);
-		assert (col != null);
-
-		QIcon ico = makeColorIcon (col, 16);
-		box->addItem (ico, fmt ("[%1] %2 (%3 object%4)",
-			pair.first, col->name, pair.second, plural (pair.second)));
-		box->setItemData (row, pair.first);
-
-		++row;
-	}
-}
-
-void ForgeWindow::setStatusBarText (str text)
-{	statusBar()->showMessage (text);
-}
-
-Ui_LDForgeUI* ForgeWindow::interface() const
-{	return ui;
-}
-
-#define act(N) QAction* ForgeWindow::action##N() { return ui->action##N; }
-#include "actions.h"
-
-void ForgeWindow::updateDocumentList()
-{	ui->fileList->clear();
-
-	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 a pointer to it in
-		// the file, so we can find files by the list item.
-		ui->fileList->addItem ("");
-		QListWidgetItem* item = ui->fileList->item (ui->fileList->count() - 1);
-		f->setListItem (item);
-
-		updateDocumentListItem (f);
-	}
-}
-
-void ForgeWindow::updateDocumentListItem (LDDocument* f)
-{	if (f->getListItem() == null)
-	{	// We don't have a list item for this file, so the list either doesn't
-		// exist yet or is out of date. Build the list now.
-		updateDocumentList();
-		return;
-	}
-
-	// If this is the current file, it also needs to be the selected item on
-	// the list.
-	if (f == getCurrentDocument())
-		ui->fileList->setCurrentItem (f->getListItem());
-
-	// If we list implicit files, draw them with a shade of gray to make them
-	// distinct.
-	if (f->isImplicit())
-		f->getListItem()->setForeground (QColor (96, 96, 96));
-
-	f->getListItem()->setText (f->getShortName());
-
-	// If the document has unsaved changes, draw a little icon next to it to mark that.
-	f->getListItem()->setIcon (f->hasUnsavedChanges() ? getIcon ("file-save") : QIcon());
-}
-
-void ForgeWindow::beginAction (QAction* act)
-{	// Open the history so we can record the edits done during this action.
-	if (act == ui->actionUndo || act == ui->actionRedo || act == ui->actionOpen)
-		getCurrentDocument()->getHistory()->setIgnoring (true);
-}
-
-void ForgeWindow::endAction()
-{	// Close 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());
-}
-
-// =============================================================================
-// A file is selected from the list of files on the left of the screen. Find out
-// which file was picked and change to it.
-void ForgeWindow::changeCurrentFile()
-{	LDDocument* f = null;
-	QListWidgetItem* item = ui->fileList->currentItem();
-
-	// Find the file pointer of the item that was selected.
-	for (LDDocument* it : g_loadedFiles)
-	{	if (it->getListItem() == item)
-		{	f = it;
-			break;
-		}
-	}
-
-	// If we picked the same file we're currently on, we don't need to do
-	// anything.
-	if (!f || f == getCurrentDocument())
-		return;
-
-	LDDocument::setCurrent (f);
-}
-
-void ForgeWindow::refreshObjectList()
-{
-#if 0
-	ui->objectList->clear();
-	LDDocument* f = getCurrentDocument();
-
-for (LDObject * obj : *f)
-		ui->objectList->addItem (obj->qObjListEntry);
-
-#endif
-
-	buildObjList();
-}
-
-void ForgeWindow::updateActions()
-{	History* his = getCurrentDocument()->getHistory();
-	int pos = his->getPosition();
-	ui->actionUndo->setEnabled (pos != -1);
-	ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1);
-	ui->actionAxes->setChecked (gl_axes);
-	ui->actionBFCView->setChecked (gl_colorbfc);
-}
-
-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 getColor() == null;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui_actions.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,634 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QTextEdit>
+#include <QBoxLayout>
+#include <QDialogButtonBox>
+#include <QPushButton>
+#include <QInputDialog>
+
+#include "gui.h"
+#include "document.h"
+#include "history.h"
+#include "configDialog.h"
+#include "addObjectDialog.h"
+#include "misc.h"
+#include "gldraw.h"
+#include "dialogs.h"
+#include "primitives.h"
+#include "ui_newpart.h"
+#include "widgets.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);
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (New, CTRL_SHIFT (N))
+{	QDialog* dlg = new QDialog (g_win);
+	Ui::NewPartUI ui;
+	ui.setupUi (dlg);
+
+	str authortext = ld_defaultname;
+
+	if (!ld_defaultuser.value.isEmpty())
+		authortext.append (fmt (" [%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",
+				fmt ("Unknown ld_defaultlicense value %1!", ld_defaultlicense));
+			break;
+	}
+
+	if (dlg->exec() == false)
+		return;
+
+	newFile();
+
+	const LDBFC::Type BFCType =
+		ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW :
+		ui.rb_bfc_cw->isChecked()  ? LDBFC::CertifyCW : LDBFC::NoCertify;
+
+	const str license =
+		ui.rb_license_ca->isChecked()    ? CALicense :
+		ui.rb_license_nonca->isChecked() ? NonCALicense : "";
+
+	getCurrentDocument()->addObjects (
+	{	new LDComment (ui.le_title->text()),
+		new LDComment ("Name: <untitled>.dat"),
+		new LDComment (fmt ("Author: %1", ui.le_author->text())),
+		new LDComment (fmt ("!LDRAW_ORG Unofficial_Part")),
+		(license != "" ? new LDComment (license) : null),
+		new LDEmpty,
+		new LDBFC (BFCType),
+		new LDEmpty,
+	});
+
+	g_win->doFullRefresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (NewFile, CTRL (N))
+{	newFile();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Open, CTRL (O))
+{	str name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)");
+
+	if (name.length() == 0)
+		return;
+
+	openMainFile (name);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Save, CTRL (S))
+{	g_win->save (getCurrentDocument(), false);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (SaveAs, CTRL_SHIFT (S))
+{	g_win->save (getCurrentDocument(), true);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (SaveAll, CTRL (L))
+{	for (LDDocument* file : g_loadedFiles)
+	{	if (file->isImplicit())
+			continue;
+
+		g_win->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::Subfile, null);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (NewLine, 0)
+{	AddObjectDialog::staticDialog (LDObject::Line, null);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (NewTriangle, 0)
+{	AddObjectDialog::staticDialog (LDObject::Triangle, null);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (NewQuad, 0)
+{	AddObjectDialog::staticDialog (LDObject::Quad, null);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (NewCLine, 0)
+{	AddObjectDialog::staticDialog (LDObject::CondLine, null);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (NewComment, 0)
+{	AddObjectDialog::staticDialog (LDObject::Comment, null);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (NewBFC, 0)
+{	AddObjectDialog::staticDialog (LDObject::BFC, null);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (NewVertex, 0)
+{	AddObjectDialog::staticDialog (LDObject::Vertex, null);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Edit, 0)
+{	if (selection().size() != 1)
+		return;
+
+	LDObject* obj = selection() [0];
+	AddObjectDialog::staticDialog (obj->getType(), 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()->getObjects())
+		obj->select();
+
+	g_win->updateSelection();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A))
+{	int colnum = g_win->getSelectedColor();
+
+	if (colnum == -1)
+		return; // no consensus on color
+
+	getCurrentDocument()->clearSelection();
+
+	for (LDObject* obj : getCurrentDocument()->getObjects())
+		if (obj->getColor() == colnum)
+			obj->select();
+
+	g_win->updateSelection();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (SelectByType, 0)
+{	if (selection().isEmpty())
+		return;
+
+	LDObject::Type type = g_win->getUniformSelectedType();
+
+	if (type == LDObject::Unidentified)
+		return;
+
+	// If we're selecting subfile references, the reference filename must also
+	// be uniform.
+	str refName;
+
+	if (type == LDObject::Subfile)
+	{	refName = static_cast<LDSubfile*> (selection()[0])->getFileInfo()->getName();
+
+		for (LDObject* obj : selection())
+			if (static_cast<LDSubfile*> (obj)->getFileInfo()->getName() != refName)
+				return;
+	}
+
+	getCurrentDocument()->clearSelection();
+
+	for (LDObject* obj : getCurrentDocument()->getObjects())
+	{	if (obj->getType() != type)
+			continue;
+
+		if (type == LDObject::Subfile && static_cast<LDSubfile*> (obj)->getFileInfo()->getName() != refName)
+			continue;
+
+		obj->select();
+	}
+
+	g_win->updateSelection();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (GridCoarse, 0)
+{	grid = Grid::Coarse;
+	g_win->updateGridToolBar();
+}
+
+DEFINE_ACTION (GridMedium, 0)
+{	grid = Grid::Medium;
+	g_win->updateGridToolBar();
+}
+
+DEFINE_ACTION (GridFine, 0)
+{	grid = Grid::Fine;
+	g_win->updateGridToolBar();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (ResetView, CTRL (0))
+{	g_win->R()->resetAngles();
+	g_win->R()->update();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (InsertFrom, 0)
+{	str fname = QFileDialog::getOpenFileName();
+	int idx = g_win->getInsertionPoint();
+
+	if (!fname.length())
+		return;
+
+	File f (fname, File::Read);
+
+	if (!f)
+	{	critical (fmt ("Couldn't open %1 (%2)", fname, strerror (errno)));
+		return;
+	}
+
+	QList<LDObject*> objs = loadFileContents (&f, null);
+
+	getCurrentDocument()->clearSelection();
+
+	for (LDObject* obj : objs)
+	{	getCurrentDocument()->insertObj (idx, obj);
+		obj->select();
+		g_win->R()->compileObject (obj);
+
+		idx++;
+	}
+
+	g_win->refresh();
+	g_win->scrollToSelection();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (ExportTo, 0)
+{	if (selection().isEmpty())
+		return;
+
+	str fname = QFileDialog::getSaveFileName();
+
+	if (fname.length() == 0)
+		return;
+
+	QFile file (fname);
+
+	if (!file.open (QIODevice::WriteOnly | QIODevice::Text))
+	{	critical (fmt ("Unable to open %1 for writing (%2)", fname, strerror (errno)));
+		return;
+	}
+
+	for (LDObject* obj : selection())
+	{	str contents = obj->raw();
+		QByteArray data = contents.toUtf8();
+		file.write (data, data.size());
+		file.write ("\r\n", 2);
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (InsertRaw, 0)
+{	int idx = g_win->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 (str line : str (te_edit->toPlainText()).split ("\n"))
+	{	LDObject* obj = parseLine (line);
+
+		getCurrentDocument()->insertObj (idx, obj);
+		obj->select();
+		g_win->R()->compileObject (obj);
+		idx++;
+	}
+
+	g_win->refresh();
+	g_win->scrollToSelection();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Screenshot, 0)
+{	setlocale (LC_ALL, "C");
+
+	int w, h;
+	uchar* imgdata = g_win->R()->getScreencap (w, h);
+	QImage img = imageFromScreencap (imgdata, w, h);
+
+	str root = basename (getCurrentDocument()->getName());
+
+	if (root.right (4) == ".dat")
+		root.chop (4);
+
+	str defaultname = (root.length() > 0) ? fmt ("%1.png", root) : "";
+	str 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 (fmt ("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;
+	g_win->updateActions();
+	g_win->R()->update();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (VisibilityToggle, 0)
+{	for (LDObject* obj : selection())
+		obj->toggleHidden();
+
+	g_win->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (VisibilityHide, 0)
+{	for (LDObject* obj : selection())
+		obj->setHidden (true);
+	
+	g_win->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (VisibilityReveal, 0)
+{	for (LDObject* obj : selection())
+	obj->setHidden (false);
+	
+	g_win->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Wireframe, 0)
+{	gl_wireframe = !gl_wireframe;
+	g_win->R()->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (SetOverlay,  0)
+{	OverlayDialog dlg;
+
+	if (!dlg.exec())
+		return;
+
+	g_win->R()->setupOverlay ((GL::EFixedCamera) dlg.camera(), dlg.fpath(), dlg.ofsx(),
+		dlg.ofsy(), dlg.lwidth(), dlg.lheight());
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (ClearOverlay, 0)
+{	g_win->R()->clearOverlay();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (ModeSelect, CTRL (1))
+{	g_win->R()->setEditMode (ESelectMode);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (ModeDraw, CTRL (2))
+{	g_win->R()->setEditMode (EDrawMode);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (ModeCircle, CTRL (3))
+{	g_win->R()->setEditMode (ECircleMode);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (SetDrawDepth, 0)
+{	if (g_win->R()->camera() == GL::EFreeCamera)
+		return;
+
+	bool ok;
+	double depth = QInputDialog::getDouble (g_win, "Set Draw Depth",
+											fmt ("Depth value for %1 Camera:", g_win->R()->getCameraName()),
+											g_win->R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok);
+
+	if (ok)
+		g_win->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)
+{	PrimitiveLister::start();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (BFCView, SHIFT (B))
+{	gl_colorbfc = !gl_colorbfc;
+	g_win->updateActions();
+	g_win->R()->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (JumpTo, CTRL (G))
+{	bool ok;
+	int defval = 0;
+	LDObject* obj;
+
+	if (selection().size() == 1)
+		defval = selection()[0]->getIndex();
+
+	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();
+	g_win->updateSelection();
+}
--- a/src/gui_actions.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,634 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QFileDialog>
-#include <QMessageBox>
-#include <QTextEdit>
-#include <QBoxLayout>
-#include <QDialogButtonBox>
-#include <QPushButton>
-#include <QInputDialog>
-
-#include "gui.h"
-#include "document.h"
-#include "history.h"
-#include "configDialog.h"
-#include "addObjectDialog.h"
-#include "misc.h"
-#include "gldraw.h"
-#include "dialogs.h"
-#include "primitives.h"
-#include "ui_newpart.h"
-#include "widgets.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);
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (New, CTRL_SHIFT (N))
-{	QDialog* dlg = new QDialog (g_win);
-	Ui::NewPartUI ui;
-	ui.setupUi (dlg);
-
-	str authortext = ld_defaultname;
-
-	if (!ld_defaultuser.value.isEmpty())
-		authortext.append (fmt (" [%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",
-				fmt ("Unknown ld_defaultlicense value %1!", ld_defaultlicense));
-			break;
-	}
-
-	if (dlg->exec() == false)
-		return;
-
-	newFile();
-
-	const LDBFC::Type BFCType =
-		ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW :
-		ui.rb_bfc_cw->isChecked()  ? LDBFC::CertifyCW : LDBFC::NoCertify;
-
-	const str license =
-		ui.rb_license_ca->isChecked()    ? CALicense :
-		ui.rb_license_nonca->isChecked() ? NonCALicense : "";
-
-	getCurrentDocument()->addObjects (
-	{	new LDComment (ui.le_title->text()),
-		new LDComment ("Name: <untitled>.dat"),
-		new LDComment (fmt ("Author: %1", ui.le_author->text())),
-		new LDComment (fmt ("!LDRAW_ORG Unofficial_Part")),
-		(license != "" ? new LDComment (license) : null),
-		new LDEmpty,
-		new LDBFC (BFCType),
-		new LDEmpty,
-	});
-
-	g_win->doFullRefresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (NewFile, CTRL (N))
-{	newFile();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Open, CTRL (O))
-{	str name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)");
-
-	if (name.length() == 0)
-		return;
-
-	openMainFile (name);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Save, CTRL (S))
-{	g_win->save (getCurrentDocument(), false);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (SaveAs, CTRL_SHIFT (S))
-{	g_win->save (getCurrentDocument(), true);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (SaveAll, CTRL (L))
-{	for (LDDocument* file : g_loadedFiles)
-	{	if (file->isImplicit())
-			continue;
-
-		g_win->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::Subfile, null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (NewLine, 0)
-{	AddObjectDialog::staticDialog (LDObject::Line, null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (NewTriangle, 0)
-{	AddObjectDialog::staticDialog (LDObject::Triangle, null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (NewQuad, 0)
-{	AddObjectDialog::staticDialog (LDObject::Quad, null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (NewCLine, 0)
-{	AddObjectDialog::staticDialog (LDObject::CondLine, null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (NewComment, 0)
-{	AddObjectDialog::staticDialog (LDObject::Comment, null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (NewBFC, 0)
-{	AddObjectDialog::staticDialog (LDObject::BFC, null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (NewVertex, 0)
-{	AddObjectDialog::staticDialog (LDObject::Vertex, null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Edit, 0)
-{	if (selection().size() != 1)
-		return;
-
-	LDObject* obj = selection() [0];
-	AddObjectDialog::staticDialog (obj->getType(), 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()->getObjects())
-		obj->select();
-
-	g_win->updateSelection();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A))
-{	int colnum = g_win->getSelectedColor();
-
-	if (colnum == -1)
-		return; // no consensus on color
-
-	getCurrentDocument()->clearSelection();
-
-	for (LDObject* obj : getCurrentDocument()->getObjects())
-		if (obj->getColor() == colnum)
-			obj->select();
-
-	g_win->updateSelection();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (SelectByType, 0)
-{	if (selection().isEmpty())
-		return;
-
-	LDObject::Type type = g_win->getUniformSelectedType();
-
-	if (type == LDObject::Unidentified)
-		return;
-
-	// If we're selecting subfile references, the reference filename must also
-	// be uniform.
-	str refName;
-
-	if (type == LDObject::Subfile)
-	{	refName = static_cast<LDSubfile*> (selection()[0])->getFileInfo()->getName();
-
-		for (LDObject* obj : selection())
-			if (static_cast<LDSubfile*> (obj)->getFileInfo()->getName() != refName)
-				return;
-	}
-
-	getCurrentDocument()->clearSelection();
-
-	for (LDObject* obj : getCurrentDocument()->getObjects())
-	{	if (obj->getType() != type)
-			continue;
-
-		if (type == LDObject::Subfile && static_cast<LDSubfile*> (obj)->getFileInfo()->getName() != refName)
-			continue;
-
-		obj->select();
-	}
-
-	g_win->updateSelection();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (GridCoarse, 0)
-{	grid = Grid::Coarse;
-	g_win->updateGridToolBar();
-}
-
-DEFINE_ACTION (GridMedium, 0)
-{	grid = Grid::Medium;
-	g_win->updateGridToolBar();
-}
-
-DEFINE_ACTION (GridFine, 0)
-{	grid = Grid::Fine;
-	g_win->updateGridToolBar();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (ResetView, CTRL (0))
-{	g_win->R()->resetAngles();
-	g_win->R()->update();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (InsertFrom, 0)
-{	str fname = QFileDialog::getOpenFileName();
-	int idx = g_win->getInsertionPoint();
-
-	if (!fname.length())
-		return;
-
-	File f (fname, File::Read);
-
-	if (!f)
-	{	critical (fmt ("Couldn't open %1 (%2)", fname, strerror (errno)));
-		return;
-	}
-
-	QList<LDObject*> objs = loadFileContents (&f, null);
-
-	getCurrentDocument()->clearSelection();
-
-	for (LDObject* obj : objs)
-	{	getCurrentDocument()->insertObj (idx, obj);
-		obj->select();
-		g_win->R()->compileObject (obj);
-
-		idx++;
-	}
-
-	g_win->refresh();
-	g_win->scrollToSelection();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (ExportTo, 0)
-{	if (selection().isEmpty())
-		return;
-
-	str fname = QFileDialog::getSaveFileName();
-
-	if (fname.length() == 0)
-		return;
-
-	QFile file (fname);
-
-	if (!file.open (QIODevice::WriteOnly | QIODevice::Text))
-	{	critical (fmt ("Unable to open %1 for writing (%2)", fname, strerror (errno)));
-		return;
-	}
-
-	for (LDObject* obj : selection())
-	{	str contents = obj->raw();
-		QByteArray data = contents.toUtf8();
-		file.write (data, data.size());
-		file.write ("\r\n", 2);
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (InsertRaw, 0)
-{	int idx = g_win->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 (str line : str (te_edit->toPlainText()).split ("\n"))
-	{	LDObject* obj = parseLine (line);
-
-		getCurrentDocument()->insertObj (idx, obj);
-		obj->select();
-		g_win->R()->compileObject (obj);
-		idx++;
-	}
-
-	g_win->refresh();
-	g_win->scrollToSelection();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Screenshot, 0)
-{	setlocale (LC_ALL, "C");
-
-	int w, h;
-	uchar* imgdata = g_win->R()->getScreencap (w, h);
-	QImage img = imageFromScreencap (imgdata, w, h);
-
-	str root = basename (getCurrentDocument()->getName());
-
-	if (root.right (4) == ".dat")
-		root.chop (4);
-
-	str defaultname = (root.length() > 0) ? fmt ("%1.png", root) : "";
-	str 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 (fmt ("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;
-	g_win->updateActions();
-	g_win->R()->update();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (VisibilityToggle, 0)
-{	for (LDObject* obj : selection())
-		obj->toggleHidden();
-
-	g_win->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (VisibilityHide, 0)
-{	for (LDObject* obj : selection())
-		obj->setHidden (true);
-	
-	g_win->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (VisibilityReveal, 0)
-{	for (LDObject* obj : selection())
-	obj->setHidden (false);
-	
-	g_win->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Wireframe, 0)
-{	gl_wireframe = !gl_wireframe;
-	g_win->R()->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (SetOverlay,  0)
-{	OverlayDialog dlg;
-
-	if (!dlg.exec())
-		return;
-
-	g_win->R()->setupOverlay ((GL::EFixedCamera) dlg.camera(), dlg.fpath(), dlg.ofsx(),
-		dlg.ofsy(), dlg.lwidth(), dlg.lheight());
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (ClearOverlay, 0)
-{	g_win->R()->clearOverlay();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (ModeSelect, CTRL (1))
-{	g_win->R()->setEditMode (ESelectMode);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (ModeDraw, CTRL (2))
-{	g_win->R()->setEditMode (EDrawMode);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (ModeCircle, CTRL (3))
-{	g_win->R()->setEditMode (ECircleMode);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (SetDrawDepth, 0)
-{	if (g_win->R()->camera() == GL::EFreeCamera)
-		return;
-
-	bool ok;
-	double depth = QInputDialog::getDouble (g_win, "Set Draw Depth",
-											fmt ("Depth value for %1 Camera:", g_win->R()->getCameraName()),
-											g_win->R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok);
-
-	if (ok)
-		g_win->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)
-{	PrimitiveLister::start();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (BFCView, SHIFT (B))
-{	gl_colorbfc = !gl_colorbfc;
-	g_win->updateActions();
-	g_win->R()->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (JumpTo, CTRL (G))
-{	bool ok;
-	int defval = 0;
-	LDObject* obj;
-
-	if (selection().size() == 1)
-		defval = selection()[0]->getIndex();
-
-	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();
-	g_win->updateSelection();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui_editactions.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,757 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QSpinBox>
+#include <QCheckBox>
+#include <QBoxLayout>
+#include <QClipboard>
+
+#include "gui.h"
+#include "main.h"
+#include "document.h"
+#include "colorSelectDialog.h"
+#include "misc.h"
+#include "widgets.h"
+#include "gldraw.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()
+{	QList<LDObject*> objs = selection();
+	int num = 0;
+
+	// Clear the clipboard first.
+	qApp->clipboard()->clear();
+
+	// Now, copy the contents into the clipboard.
+	str data;
+
+	for (LDObject* obj : objs)
+	{	if (data.length() > 0)
+			data += "\n";
+
+		data += obj->raw();
+		++num;
+	}
+
+	qApp->clipboard()->setText (data);
+	return num;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Cut, CTRL (X))
+{	int num = copyToClipboard();
+	g_win->deleteSelection();
+	log (ForgeWindow::tr ("%1 objects cut"), num);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Copy, CTRL (C))
+{	int num = copyToClipboard();
+	log (ForgeWindow::tr ("%1 objects copied"), num);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Paste, CTRL (V))
+{	const str clipboardText = qApp->clipboard()->text();
+	int idx = g_win->getInsertionPoint();
+	getCurrentDocument()->clearSelection();
+	int num = 0;
+
+	for (str line : clipboardText.split ("\n"))
+	{	LDObject* pasted = parseLine (line);
+		getCurrentDocument()->insertObj (idx++, pasted);
+		pasted->select();
+		g_win->R()->compileObject (pasted);
+		++num;
+	}
+
+	log (ForgeWindow::tr ("%1 objects pasted"), num);
+	g_win->refresh();
+	g_win->scrollToSelection();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Delete, KEY (Delete))
+{	int num = g_win->deleteSelection();
+	log (ForgeWindow::tr ("%1 objects deleted"), num);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static void doInline (bool deep)
+{	QList<LDObject*> sel = selection();
+
+	for (LDObject* obj : sel)
+	{	// Get the index of the subfile so we know where to insert the
+		// inlined contents.
+		long idx = obj->getIndex();
+
+		if (idx == -1)
+			continue;
+
+		QList<LDObject*> objs;
+
+		if (obj->getType() == LDObject::Subfile)
+			objs = static_cast<LDSubfile*> (obj)->inlineContents (
+					   (LDSubfile::InlineFlags)
+					   ( (deep) ? LDSubfile::DeepInline : 0) |
+					   LDSubfile::CacheInline
+				   );
+		else
+			continue;
+
+		// Merge in the inlined objects
+		for (LDObject * inlineobj : objs)
+		{	str line = inlineobj->raw();
+			delete inlineobj;
+
+			LDObject* newobj = parseLine (line);
+			getCurrentDocument()->insertObj (idx++, newobj);
+			newobj->select();
+			g_win->R()->compileObject (newobj);
+		}
+
+		// Delete the subfile now as it's been inlined.
+		getCurrentDocument()->forgetObject (obj);
+		delete obj;
+	}
+
+	g_win->refresh();
+}
+
+DEFINE_ACTION (Inline, CTRL (I))
+{	doInline (false);
+}
+
+DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I))
+{	doInline (true);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (SplitQuads, 0)
+{	QList<LDObject*> objs = selection();
+	int num = 0;
+
+	for (LDObject* obj : objs)
+	{	if (obj->getType() != LDObject::Quad)
+			continue;
+
+		// Find the index of this quad
+		long index = obj->getIndex();
+
+		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)
+			g_win->R()->compileObject (t);
+
+		// Delete this quad now, it has been split.
+		delete obj;
+
+		num++;
+	}
+
+	log ("%1 quadrilaterals split", num);
+	g_win->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->raw());
+
+	if (obj->getType() == LDObject::Error)
+		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
+	g_win->R()->compileObject (obj);
+	g_win->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (SetColor, KEY (C))
+{	if (selection().isEmpty())
+		return;
+
+	int colnum;
+	int defcol = -1;
+
+	QList<LDObject*> objs = selection();
+
+	// If all selected objects have the same color, said color is our default
+	// value to the color selection dialog.
+	defcol = g_win->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);
+			g_win->R()->compileObject (obj);
+		}
+
+		g_win->refresh();
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Borders, CTRL_SHIFT (B))
+{	QList<LDObject*> objs = selection();
+	int num = 0;
+
+	for (LDObject* obj : objs)
+	{	const LDObject::Type type = obj->getType();
+		if (type != LDObject::Quad && type != LDObject::Triangle)
+			continue;
+
+		int numLines;
+		LDLine* lines[4];
+
+		if (type == LDObject::Quad)
+		{	numLines = 4;
+
+			LDQuad* quad = static_cast<LDQuad*> (obj);
+			lines[0] = new LDLine (quad->getVertex (0), quad->getVertex (1));
+			lines[1] = new LDLine (quad->getVertex (1), quad->getVertex (2));
+			lines[2] = new LDLine (quad->getVertex (2), quad->getVertex (3));
+			lines[3] = new LDLine (quad->getVertex (3), quad->getVertex (0));
+		}
+		else
+		{	numLines = 3;
+
+			LDTriangle* tri = static_cast<LDTriangle*> (obj);
+			lines[0] = new LDLine (tri->getVertex (0), tri->getVertex (1));
+			lines[1] = new LDLine (tri->getVertex (1), tri->getVertex (2));
+			lines[2] = new LDLine (tri->getVertex (2), tri->getVertex (0));
+		}
+
+		for (int i = 0; i < numLines; ++i)
+		{	long idx = obj->getIndex() + i + 1;
+
+			lines[i]->setColor (edgecolor);
+			getCurrentDocument()->insertObj (idx, lines[i]);
+			g_win->R()->compileObject (lines[i]);
+		}
+
+		num += numLines;
+	}
+
+	log (ForgeWindow::tr ("Added %1 border lines"), num);
+	g_win->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (CornerVerts, 0)
+{	int num = 0;
+
+	for (LDObject* obj : selection())
+	{	if (obj->vertices() < 2)
+			continue;
+
+		int idx = obj->getIndex();
+
+		for (int i = 0; i < obj->vertices(); ++i)
+		{	LDVertex* vert = new LDVertex;
+			vert->pos = obj->getVertex (i);
+			vert->setColor (obj->getColor());
+
+			getCurrentDocument()->insertObj (++idx, vert);
+			g_win->R()->compileObject (vert);
+			++num;
+		}
+	}
+
+	log (ForgeWindow::tr ("Added %1 vertices"), num);
+	g_win->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static void doMoveSelection (const bool up)
+{	QList<LDObject*> 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]->value;
+	vect[Y] *= currentGrid().confs[Grid::Y]->value;
+	vect[Z] *= currentGrid().confs[Grid::Z]->value;
+
+	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))
+{	QList<LDObject*> sel = selection();
+
+	for (LDObject* obj : sel)
+	{	obj->invert();
+		g_win->R()->compileObject (obj);
+	}
+
+	g_win->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)
+{	QList<LDObject*> sel = selection();
+	QList<vertex*> queue;
+	const vertex rotpoint = rotPoint (sel);
+	const double angle = (pi * currentGrid().confs[Grid::Angle]->value) / 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->getVertex (i);
+				rotateVertex (v, rotpoint, transform);
+				obj->setVertex (i, v);
+			}
+		} elif (obj->hasMatrix())
+		{	LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
+
+			// Transform the position
+			vertex v = mo->getPosition();
+			rotateVertex (v, rotpoint, transform);
+			mo->setPosition (v);
+
+			// Transform the matrix
+			mo->setTransform (mo->getTransform() * transform);
+		} elif (obj->getType() == LDObject::Vertex)
+		{	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->getPosition();
+			matrix t = mo->getTransform();
+
+			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->getVertex (i);
+
+				for_axes (ax)
+					roundToDecimals (v[ax], 3);
+
+				obj->setVertex (i, v);
+				g_win->R()->compileObject (obj);
+				num += 3;
+			}
+		}
+	}
+
+	log (ForgeWindow::tr ("Rounded %1 values"), num);
+	g_win->refreshObjectList();
+	g_win->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Uncolorize, 0)
+{	int num = 0;
+
+	for (LDObject* obj : selection())
+	{	if (obj->isColored() == false)
+			continue;
+
+		int col = maincolor;
+
+		if (obj->getType() == LDObject::Line || obj->getType() == LDObject::CondLine)
+			col = edgecolor;
+
+		obj->setColor (col);
+		g_win->R()->compileObject (obj);
+		num++;
+	}
+
+	log (ForgeWindow::tr ("%1 objects uncolored"), num);
+	g_win->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->getVertex (i);
+
+			for (Axis ax : sel)
+			{	double& coord = v[ax];
+
+				if (any || coord == search)
+				{	if (!rel)
+						coord = 0;
+
+					coord += replacement;
+					num++;
+				}
+			}
+
+			obj->setVertex (i, v);
+			g_win->R()->compileObject (obj);
+		}
+	}
+
+	log (ForgeWindow::tr ("Altered %1 values"), num);
+	g_win->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->getVertex (i);
+
+			for (Axis ax : sel)
+				v[ax] *= -1;
+
+			obj->setVertex (i, v);
+			g_win->R()->compileObject (obj);
+		}
+	}
+
+	g_win->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Demote, 0)
+{	QList<LDObject*> sel = selection();
+	int num = 0;
+
+	for (LDObject* obj : sel)
+	{	if (obj->getType() != LDObject::CondLine)
+			continue;
+
+		LDLine* repl = static_cast<LDCondLine*> (obj)->demote();
+		g_win->R()->compileObject (repl);
+		++num;
+	}
+
+	log (ForgeWindow::tr ("Demoted %1 conditional lines"), num);
+	g_win->refresh();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static bool isColorUsed (int colnum)
+{	for (LDObject* obj : getCurrentDocument()->getObjects())
+		if (obj->isColored() && obj->getColor() == 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)
+	{	log (ForgeWindow::tr ("Cannot auto-color: all colors are in use!"));
+		return;
+	}
+
+	for (LDObject* obj : selection())
+	{	if (obj->isColored() == false)
+			continue;
+
+		obj->setColor (colnum);
+		g_win->R()->compileObject (obj);
+	}
+
+	log (ForgeWindow::tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name);
+	g_win->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
+	str commentText = fmt ("!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 && 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->getIndex() : 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);
+
+	g_win->buildObjList();
+	delete ui;
+}
--- a/src/gui_editactions.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,757 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QSpinBox>
-#include <QCheckBox>
-#include <QBoxLayout>
-#include <QClipboard>
-
-#include "gui.h"
-#include "main.h"
-#include "document.h"
-#include "colorSelectDialog.h"
-#include "misc.h"
-#include "widgets.h"
-#include "gldraw.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()
-{	QList<LDObject*> objs = selection();
-	int num = 0;
-
-	// Clear the clipboard first.
-	qApp->clipboard()->clear();
-
-	// Now, copy the contents into the clipboard.
-	str data;
-
-	for (LDObject* obj : objs)
-	{	if (data.length() > 0)
-			data += "\n";
-
-		data += obj->raw();
-		++num;
-	}
-
-	qApp->clipboard()->setText (data);
-	return num;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Cut, CTRL (X))
-{	int num = copyToClipboard();
-	g_win->deleteSelection();
-	log (ForgeWindow::tr ("%1 objects cut"), num);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Copy, CTRL (C))
-{	int num = copyToClipboard();
-	log (ForgeWindow::tr ("%1 objects copied"), num);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Paste, CTRL (V))
-{	const str clipboardText = qApp->clipboard()->text();
-	int idx = g_win->getInsertionPoint();
-	getCurrentDocument()->clearSelection();
-	int num = 0;
-
-	for (str line : clipboardText.split ("\n"))
-	{	LDObject* pasted = parseLine (line);
-		getCurrentDocument()->insertObj (idx++, pasted);
-		pasted->select();
-		g_win->R()->compileObject (pasted);
-		++num;
-	}
-
-	log (ForgeWindow::tr ("%1 objects pasted"), num);
-	g_win->refresh();
-	g_win->scrollToSelection();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Delete, KEY (Delete))
-{	int num = g_win->deleteSelection();
-	log (ForgeWindow::tr ("%1 objects deleted"), num);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static void doInline (bool deep)
-{	QList<LDObject*> sel = selection();
-
-	for (LDObject* obj : sel)
-	{	// Get the index of the subfile so we know where to insert the
-		// inlined contents.
-		long idx = obj->getIndex();
-
-		if (idx == -1)
-			continue;
-
-		QList<LDObject*> objs;
-
-		if (obj->getType() == LDObject::Subfile)
-			objs = static_cast<LDSubfile*> (obj)->inlineContents (
-					   (LDSubfile::InlineFlags)
-					   ( (deep) ? LDSubfile::DeepInline : 0) |
-					   LDSubfile::CacheInline
-				   );
-		else
-			continue;
-
-		// Merge in the inlined objects
-		for (LDObject * inlineobj : objs)
-		{	str line = inlineobj->raw();
-			delete inlineobj;
-
-			LDObject* newobj = parseLine (line);
-			getCurrentDocument()->insertObj (idx++, newobj);
-			newobj->select();
-			g_win->R()->compileObject (newobj);
-		}
-
-		// Delete the subfile now as it's been inlined.
-		getCurrentDocument()->forgetObject (obj);
-		delete obj;
-	}
-
-	g_win->refresh();
-}
-
-DEFINE_ACTION (Inline, CTRL (I))
-{	doInline (false);
-}
-
-DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I))
-{	doInline (true);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (SplitQuads, 0)
-{	QList<LDObject*> objs = selection();
-	int num = 0;
-
-	for (LDObject* obj : objs)
-	{	if (obj->getType() != LDObject::Quad)
-			continue;
-
-		// Find the index of this quad
-		long index = obj->getIndex();
-
-		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)
-			g_win->R()->compileObject (t);
-
-		// Delete this quad now, it has been split.
-		delete obj;
-
-		num++;
-	}
-
-	log ("%1 quadrilaterals split", num);
-	g_win->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->raw());
-
-	if (obj->getType() == LDObject::Error)
-		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
-	g_win->R()->compileObject (obj);
-	g_win->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (SetColor, KEY (C))
-{	if (selection().isEmpty())
-		return;
-
-	int colnum;
-	int defcol = -1;
-
-	QList<LDObject*> objs = selection();
-
-	// If all selected objects have the same color, said color is our default
-	// value to the color selection dialog.
-	defcol = g_win->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);
-			g_win->R()->compileObject (obj);
-		}
-
-		g_win->refresh();
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Borders, CTRL_SHIFT (B))
-{	QList<LDObject*> objs = selection();
-	int num = 0;
-
-	for (LDObject* obj : objs)
-	{	const LDObject::Type type = obj->getType();
-		if (type != LDObject::Quad && type != LDObject::Triangle)
-			continue;
-
-		int numLines;
-		LDLine* lines[4];
-
-		if (type == LDObject::Quad)
-		{	numLines = 4;
-
-			LDQuad* quad = static_cast<LDQuad*> (obj);
-			lines[0] = new LDLine (quad->getVertex (0), quad->getVertex (1));
-			lines[1] = new LDLine (quad->getVertex (1), quad->getVertex (2));
-			lines[2] = new LDLine (quad->getVertex (2), quad->getVertex (3));
-			lines[3] = new LDLine (quad->getVertex (3), quad->getVertex (0));
-		}
-		else
-		{	numLines = 3;
-
-			LDTriangle* tri = static_cast<LDTriangle*> (obj);
-			lines[0] = new LDLine (tri->getVertex (0), tri->getVertex (1));
-			lines[1] = new LDLine (tri->getVertex (1), tri->getVertex (2));
-			lines[2] = new LDLine (tri->getVertex (2), tri->getVertex (0));
-		}
-
-		for (int i = 0; i < numLines; ++i)
-		{	long idx = obj->getIndex() + i + 1;
-
-			lines[i]->setColor (edgecolor);
-			getCurrentDocument()->insertObj (idx, lines[i]);
-			g_win->R()->compileObject (lines[i]);
-		}
-
-		num += numLines;
-	}
-
-	log (ForgeWindow::tr ("Added %1 border lines"), num);
-	g_win->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (CornerVerts, 0)
-{	int num = 0;
-
-	for (LDObject* obj : selection())
-	{	if (obj->vertices() < 2)
-			continue;
-
-		int idx = obj->getIndex();
-
-		for (int i = 0; i < obj->vertices(); ++i)
-		{	LDVertex* vert = new LDVertex;
-			vert->pos = obj->getVertex (i);
-			vert->setColor (obj->getColor());
-
-			getCurrentDocument()->insertObj (++idx, vert);
-			g_win->R()->compileObject (vert);
-			++num;
-		}
-	}
-
-	log (ForgeWindow::tr ("Added %1 vertices"), num);
-	g_win->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static void doMoveSelection (const bool up)
-{	QList<LDObject*> 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]->value;
-	vect[Y] *= currentGrid().confs[Grid::Y]->value;
-	vect[Z] *= currentGrid().confs[Grid::Z]->value;
-
-	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))
-{	QList<LDObject*> sel = selection();
-
-	for (LDObject* obj : sel)
-	{	obj->invert();
-		g_win->R()->compileObject (obj);
-	}
-
-	g_win->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)
-{	QList<LDObject*> sel = selection();
-	QList<vertex*> queue;
-	const vertex rotpoint = rotPoint (sel);
-	const double angle = (pi * currentGrid().confs[Grid::Angle]->value) / 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->getVertex (i);
-				rotateVertex (v, rotpoint, transform);
-				obj->setVertex (i, v);
-			}
-		} elif (obj->hasMatrix())
-		{	LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
-
-			// Transform the position
-			vertex v = mo->getPosition();
-			rotateVertex (v, rotpoint, transform);
-			mo->setPosition (v);
-
-			// Transform the matrix
-			mo->setTransform (mo->getTransform() * transform);
-		} elif (obj->getType() == LDObject::Vertex)
-		{	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->getPosition();
-			matrix t = mo->getTransform();
-
-			for (const Axis ax : g_Axes)
-				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->getVertex (i);
-
-				for (const Axis ax : g_Axes)
-					roundToDecimals (v[ax], 3);
-
-				obj->setVertex (i, v);
-				g_win->R()->compileObject (obj);
-				num += 3;
-			}
-		}
-	}
-
-	log (ForgeWindow::tr ("Rounded %1 values"), num);
-	g_win->refreshObjectList();
-	g_win->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Uncolorize, 0)
-{	int num = 0;
-
-	for (LDObject* obj : selection())
-	{	if (obj->isColored() == false)
-			continue;
-
-		int col = maincolor;
-
-		if (obj->getType() == LDObject::Line || obj->getType() == LDObject::CondLine)
-			col = edgecolor;
-
-		obj->setColor (col);
-		g_win->R()->compileObject (obj);
-		num++;
-	}
-
-	log (ForgeWindow::tr ("%1 objects uncolored"), num);
-	g_win->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->getVertex (i);
-
-			for (Axis ax : sel)
-			{	double& coord = v[ax];
-
-				if (any || coord == search)
-				{	if (!rel)
-						coord = 0;
-
-					coord += replacement;
-					num++;
-				}
-			}
-
-			obj->setVertex (i, v);
-			g_win->R()->compileObject (obj);
-		}
-	}
-
-	log (ForgeWindow::tr ("Altered %1 values"), num);
-	g_win->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->getVertex (i);
-
-			for (Axis ax : sel)
-				v[ax] *= -1;
-
-			obj->setVertex (i, v);
-			g_win->R()->compileObject (obj);
-		}
-	}
-
-	g_win->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-DEFINE_ACTION (Demote, 0)
-{	QList<LDObject*> sel = selection();
-	int num = 0;
-
-	for (LDObject* obj : sel)
-	{	if (obj->getType() != LDObject::CondLine)
-			continue;
-
-		LDLine* repl = static_cast<LDCondLine*> (obj)->demote();
-		g_win->R()->compileObject (repl);
-		++num;
-	}
-
-	log (ForgeWindow::tr ("Demoted %1 conditional lines"), num);
-	g_win->refresh();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static bool isColorUsed (int colnum)
-{	for (LDObject* obj : getCurrentDocument()->getObjects())
-		if (obj->isColored() && obj->getColor() == 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)
-	{	log (ForgeWindow::tr ("Cannot auto-color: all colors are in use!"));
-		return;
-	}
-
-	for (LDObject* obj : selection())
-	{	if (obj->isColored() == false)
-			continue;
-
-		obj->setColor (colnum);
-		g_win->R()->compileObject (obj);
-	}
-
-	log (ForgeWindow::tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name);
-	g_win->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
-	str commentText = fmt ("!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 && 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->getIndex() : 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);
-
-	g_win->buildObjList();
-	delete ui;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/history.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,183 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "history.h"
+#include "ldtypes.h"
+#include "document.h"
+#include "misc.h"
+#include "gui.h"
+#include "gldraw.h"
+
+bool g_fullRefresh = false;
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+History::History() :
+	m_Position (-1) {}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void History::undo()
+{	if (m_changesets.isEmpty() || getPosition() == -1)
+		return;
+
+	const Changeset& set = getChangeset (getPosition());
+	g_fullRefresh = false;
+
+	// Iterate the list in reverse and undo all actions
+	for (auto it = set.end() - 1; it != set.begin(); --it)
+		(*it)->undo();
+
+	decreasePosition();
+
+	if (!g_fullRefresh)
+		g_win->refresh();
+	else
+		g_win->doFullRefresh();
+
+	g_win->updateActions();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void History::redo()
+{	if (getPosition() == (long) m_changesets.size())
+		return;
+
+	const Changeset& set = getChangeset (getPosition() + 1);
+	g_fullRefresh = false;
+
+	// Redo things - in the order as they were done in the first place
+	for (const AbstractHistoryEntry* change : set)
+		change->redo();
+
+	setPosition (getPosition() + 1);
+
+	if (!g_fullRefresh)
+		g_win->refresh();
+	else
+		g_win->doFullRefresh();
+
+	g_win->updateActions();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void History::clear()
+{	for (Changeset set : m_changesets)
+		for (AbstractHistoryEntry* change : set)
+			delete change;
+
+	m_changesets.clear();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void History::addStep()
+{	if (m_currentChangeset.isEmpty())
+		return;
+
+	while (getPosition() < getSize() - 1)
+		m_changesets.removeLast();
+
+	m_changesets << m_currentChangeset;
+	m_currentChangeset.clear();
+	setPosition (getPosition() + 1);
+	g_win->updateActions();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void History::add (AbstractHistoryEntry* entry)
+{	if (isIgnoring())
+	{	delete entry;
+		return;
+	}
+
+	entry->setParent (this);
+	m_currentChangeset << entry;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void AddHistory::undo() const
+{	LDDocument* f = getParent()->getFile();
+	LDObject* obj = f->getObject (getIndex());
+	f->forgetObject (obj);
+	delete obj;
+
+	g_fullRefresh = true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void AddHistory::redo() const
+{	LDDocument* f = getParent()->getFile();
+	LDObject* obj = parseLine (getCode());
+	f->insertObj (getIndex(), obj);
+	g_win->R()->compileObject (obj);
+}
+
+// =============================================================================
+// heh
+// -----------------------------------------------------------------------------
+void DelHistory::undo() const
+{	LDDocument* f = getParent()->getFile();
+	LDObject* obj = parseLine (getCode());
+	f->insertObj (getIndex(), obj);
+	g_win->R()->compileObject (obj);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void DelHistory::redo() const
+{	LDDocument* f = getParent()->getFile();
+	LDObject* obj = f->getObject (getIndex());
+	f->forgetObject (obj);
+	delete obj;
+
+	g_fullRefresh = true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void EditHistory::undo() const
+{	LDObject* obj = getCurrentDocument()->getObject (getIndex());
+	LDObject* newobj = parseLine (getOldCode());
+	obj->replace (newobj);
+	g_win->R()->compileObject (newobj);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void EditHistory::redo() const
+{	LDObject* obj = getCurrentDocument()->getObject (getIndex());
+	LDObject* newobj = parseLine (getNewCode());
+	obj->replace (newobj);
+	g_win->R()->compileObject (newobj);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void SwapHistory::undo() const
+{	LDObject::fromID (a)->swap (LDObject::fromID (b));
+}
+
+void SwapHistory::redo() const
+{	undo(); // :v
+}
\ No newline at end of file
--- a/src/history.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,183 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "history.h"
-#include "ldtypes.h"
-#include "document.h"
-#include "misc.h"
-#include "gui.h"
-#include "gldraw.h"
-
-bool g_fullRefresh = false;
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-History::History() :
-	m_Position (-1) {}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void History::undo()
-{	if (m_changesets.isEmpty() || getPosition() == -1)
-		return;
-
-	const Changeset& set = getChangeset (getPosition());
-	g_fullRefresh = false;
-
-	// Iterate the list in reverse and undo all actions
-	for (auto it = set.end() - 1; it != set.begin(); --it)
-		(*it)->undo();
-
-	decreasePosition();
-
-	if (!g_fullRefresh)
-		g_win->refresh();
-	else
-		g_win->doFullRefresh();
-
-	g_win->updateActions();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void History::redo()
-{	if (getPosition() == (long) m_changesets.size())
-		return;
-
-	const Changeset& set = getChangeset (getPosition() + 1);
-	g_fullRefresh = false;
-
-	// Redo things - in the order as they were done in the first place
-	for (const AbstractHistoryEntry* change : set)
-		change->redo();
-
-	setPosition (getPosition() + 1);
-
-	if (!g_fullRefresh)
-		g_win->refresh();
-	else
-		g_win->doFullRefresh();
-
-	g_win->updateActions();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void History::clear()
-{	for (Changeset set : m_changesets)
-		for (AbstractHistoryEntry* change : set)
-			delete change;
-
-	m_changesets.clear();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void History::addStep()
-{	if (m_currentChangeset.isEmpty())
-		return;
-
-	while (getPosition() < getSize() - 1)
-		m_changesets.removeLast();
-
-	m_changesets << m_currentChangeset;
-	m_currentChangeset.clear();
-	setPosition (getPosition() + 1);
-	g_win->updateActions();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void History::add (AbstractHistoryEntry* entry)
-{	if (isIgnoring())
-	{	delete entry;
-		return;
-	}
-
-	entry->setParent (this);
-	m_currentChangeset << entry;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void AddHistory::undo() const
-{	LDDocument* f = getParent()->getFile();
-	LDObject* obj = f->getObject (getIndex());
-	f->forgetObject (obj);
-	delete obj;
-
-	g_fullRefresh = true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void AddHistory::redo() const
-{	LDDocument* f = getParent()->getFile();
-	LDObject* obj = parseLine (getCode());
-	f->insertObj (getIndex(), obj);
-	g_win->R()->compileObject (obj);
-}
-
-// =============================================================================
-// heh
-// -----------------------------------------------------------------------------
-void DelHistory::undo() const
-{	LDDocument* f = getParent()->getFile();
-	LDObject* obj = parseLine (getCode());
-	f->insertObj (getIndex(), obj);
-	g_win->R()->compileObject (obj);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void DelHistory::redo() const
-{	LDDocument* f = getParent()->getFile();
-	LDObject* obj = f->getObject (getIndex());
-	f->forgetObject (obj);
-	delete obj;
-
-	g_fullRefresh = true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void EditHistory::undo() const
-{	LDObject* obj = getCurrentDocument()->getObject (getIndex());
-	LDObject* newobj = parseLine (getOldCode());
-	obj->replace (newobj);
-	g_win->R()->compileObject (newobj);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void EditHistory::redo() const
-{	LDObject* obj = getCurrentDocument()->getObject (getIndex());
-	LDObject* newobj = parseLine (getNewCode());
-	obj->replace (newobj);
-	g_win->R()->compileObject (newobj);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void SwapHistory::undo() const
-{	LDObject::fromID (a)->swap (LDObject::fromID (b));
-}
-
-void SwapHistory::redo() const
-{	undo(); // :v
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldconfig.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,194 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "document.h"
+#include "ldconfig.h"
+#include "gui.h"
+#include "misc.h"
+#include "colors.h"
+
+// =============================================================================
+// Helper function for parseLDConfig
+// -----------------------------------------------------------------------------
+static bool parseLDConfigTag (LDConfigParser& pars, char const* tag, str& 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()
+{	File* f = openLDrawFile ("LDConfig.ldr", false);
+
+	if (!f)
+	{	critical (fmt (QObject::tr ("Unable to open LDConfig.ldr for parsing: %1"),
+			strerror (errno)));
+		return;
+	}
+
+	// Read in the lines
+	for (str line : *f)
+	{	if (line.length() == 0 || line[0] != '0')
+			continue; // empty or illogical
+
+		line.remove ('\r');
+		line.remove ('\n');
+
+		// Parse the line
+		LDConfigParser pars (line, ' ');
+
+		int code = 0, alpha = 255;
+		str 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);
+	}
+
+	delete f;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDConfigParser::LDConfigParser (str 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 (str& val, const int pos)
+{	if (pos >= m_tokens.size())
+		return false;
+
+	val = m_tokens[pos];
+	return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool LDConfigParser::getNextToken (str& val)
+{	return getToken (val, ++m_pos);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool LDConfigParser::peekNextToken (str& 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)
+{	str tok;
+
+	if (!getToken (tok, inPos))
+		return false;
+
+	return (tok == sOther);
+}
--- a/src/ldconfig.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "document.h"
-#include "ldconfig.h"
-#include "gui.h"
-#include "misc.h"
-#include "colors.h"
-
-// =============================================================================
-// Helper function for parseLDConfig
-// -----------------------------------------------------------------------------
-static bool parseLDConfigTag (LDConfigParser& pars, char const* tag, str& 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()
-{	File* f = openLDrawFile ("LDConfig.ldr", false);
-
-	if (!f)
-	{	critical (fmt (QObject::tr ("Unable to open LDConfig.ldr for parsing: %1"),
-			strerror (errno)));
-		return;
-	}
-
-	// Read in the lines
-	for (str line : *f)
-	{	if (line.length() == 0 || line[0] != '0')
-			continue; // empty or illogical
-
-		line.remove ('\r');
-		line.remove ('\n');
-
-		// Parse the line
-		LDConfigParser pars (line, ' ');
-
-		int code = 0, alpha = 255;
-		str 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);
-	}
-
-	delete f;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDConfigParser::LDConfigParser (str 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 (str& val, const int pos)
-{	if (pos >= m_tokens.size())
-		return false;
-
-	val = m_tokens[pos];
-	return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool LDConfigParser::getNextToken (str& val)
-{	return getToken (val, ++m_pos);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool LDConfigParser::peekNextToken (str& 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)
-{	str tok;
-
-	if (!getToken (tok, inPos))
-		return false;
-
-	return (tok == sOther);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldtypes.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,746 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "main.h"
+#include "ldtypes.h"
+#include "document.h"
+#include "misc.h"
+#include "gui.h"
+#include "history.h"
+#include "gldraw.h"
+#include "colors.h"
+
+cfg (String, ld_defaultname, "");
+cfg (String, ld_defaultuser, "");
+cfg (Int, ld_defaultlicense, 0);
+
+// List of all LDObjects
+QList<LDObject*> g_LDObjects;
+
+// =============================================================================
+// LDObject constructors
+// -----------------------------------------------------------------------------
+LDObject::LDObject() :
+	m_Hidden (false),
+	m_Selected (false),
+	m_Parent (null),
+	m_File (null),
+	qObjListEntry (null),
+	m_glinit (false)
+{
+	memset (m_coords, 0, sizeof m_coords);
+
+	// Determine ID
+	int32 id = 1; // 0 is invalid
+
+	for (LDObject* obj : g_LDObjects)
+		if (obj->getID() >= id)
+			id = obj->getID() + 1;
+
+	setID (id);
+	g_LDObjects << this;
+}
+
+// =============================================================================
+// Default implementations for LDObject's virtual methods. These should never be
+// actually called, for a subclass-less LDObject should never come into existance.
+// These exist only to satisfy the linker.
+// -----------------------------------------------------------------------------
+LDObject::Type LDObject::getType() const
+{	return LDObject::Unidentified;
+}
+
+bool LDObject::hasMatrix() const
+{	return false;
+}
+
+bool LDObject::isColored() const
+{	return false;
+}
+
+bool LDObject::isScemantic() const
+{	return false;
+}
+
+str LDObject::getTypeName() const
+{	return "";
+}
+
+int LDObject::vertices() const
+{	return 0;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDObject::setVertexCoord (int i, Axis ax, double value)
+{	vertex v = getVertex (i);
+	v[ax] = value;
+	setVertex (i, v);
+}
+
+LDError::LDError() {}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDComment::raw()
+{	return fmt ("0 %1", text);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDSubfile::raw()
+{	str val = fmt ("1 %1 %2 ", getColor(), getPosition());
+	val += getTransform().stringRep();
+	val += ' ';
+	val += getFileInfo()->getName();
+	return val;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDLine::raw()
+{	str val = fmt ("2 %1", getColor());
+
+	for (int i = 0; i < 2; ++i)
+		val += fmt (" %1", getVertex (i));
+
+	return val;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDTriangle::raw()
+{	str val = fmt ("3 %1", getColor());
+
+	for (int i = 0; i < 3; ++i)
+		val += fmt (" %1", getVertex (i));
+
+	return val;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDQuad::raw()
+{	str val = fmt ("4 %1", getColor());
+
+	for (int i = 0; i < 4; ++i)
+		val += fmt (" %1", getVertex (i));
+
+	return val;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDCondLine::raw()
+{	str val = fmt ("5 %1", getColor());
+
+	// Add the coordinates
+	for (int i = 0; i < 4; ++i)
+		val += fmt (" %1", getVertex (i));
+
+	return val;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDError::raw()
+{	return contents;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDVertex::raw()
+{	return fmt ("0 !LDFORGE VERTEX %1 %2", getColor(), pos);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDEmpty::raw()
+{	return "";
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+const char* LDBFC::statements[] =
+{	"CERTIFY CCW",
+	"CCW",
+	"CERTIFY CW",
+	"CW",
+	"NOCERTIFY",
+	"INVERTNEXT",
+	"CLIP",
+	"CLIP CCW",
+	"CLIP CW",
+	"NOCLIP",
+};
+
+str LDBFC::raw()
+{	return fmt ("0 BFC %1", LDBFC::statements[type]);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+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 (getVertex (0), getVertex (1), getVertex (3));
+	LDTriangle* tri2 = new LDTriangle (getVertex (1), getVertex (2), getVertex (3));
+
+	// The triangles also inherit the quad's color
+	tri1->setColor (getColor());
+	tri2->setColor (getColor());
+
+	QList<LDTriangle*> triangles;
+	triangles << tri1;
+	triangles << tri2;
+	return triangles;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDObject::replace (LDObject* other)
+{	long idx = getIndex();
+	assert (idx != -1);
+
+	// Replace the instance of the old object with the new object
+	getFile()->setObject (idx, other);
+
+	// Remove the old object
+	delete this;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDObject::swap (LDObject* other)
+{	int i = 0;
+
+	for (LDObject* obj : getFile()->getObjects())
+	{	if (obj == this)
+			getFile()->setObject (i, other);
+		elif (obj == other)
+			getFile()->setObject (i, this);
+
+		++i;
+	}
+
+	getFile()->addToHistory (new SwapHistory (getID(), other->getID()));
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+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()
+{	// 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 (getFile())
+		getFile()->forgetObject (this);
+
+	// Delete the GL lists
+	GL::deleteLists (this);
+
+	// Remove this object from the list of LDObjects
+	g_LDObjects.removeOne (this);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static void transformObject (LDObject* obj, matrix transform, vertex pos, int parentcolor)
+{	switch (obj->getType())
+	{	case LDObject::Line:
+		case LDObject::CondLine:
+		case LDObject::Triangle:
+		case LDObject::Quad:
+
+			for (int i = 0; i < obj->vertices(); ++i)
+			{	vertex v = obj->getVertex (i);
+				v.transform (transform, pos);
+				obj->setVertex (i, v);
+			}
+
+			break;
+
+		case LDObject::Subfile:
+		{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			matrix newMatrix = transform * ref->getTransform();
+			vertex newpos = ref->getPosition();
+
+			newpos.transform (transform, pos);
+			ref->setPosition (newpos);
+			ref->setTransform (newMatrix);
+		}
+		break;
+
+		default:
+			break;
+	}
+
+	if (obj->getColor() == maincolor)
+		obj->setColor (parentcolor);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+QList<LDObject*> LDSubfile::inlineContents (InlineFlags flags)
+{	QList<LDObject*> objs = getFileInfo()->inlineContents (flags);
+
+	// Transform the objects
+for (LDObject * obj : objs)
+	{	// Set the parent now so we know what inlined this.
+		obj->setParent (this);
+		transformObject (obj, getTransform(), getPosition(), getColor());
+	}
+
+	return objs;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+long LDObject::getIndex() const
+{
+#ifndef RELEASE
+	assert (getFile() != null);
+#endif
+
+	for (int i = 0; i < getFile()->getObjectCount(); ++i)
+		if (getFile()->getObject (i) == this)
+			return i;
+
+	return -1;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDObject::moveObjects (QList<LDObject*> 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;
+	QList<LDObject*> objsToCompile;
+	LDDocument* file = objs[0]->getFile();
+
+	for (long i = start; i != end; i += incr)
+	{	LDObject* obj = objs[i];
+
+		const long idx = obj->getIndex(),
+				   target = idx + (up ? -1 : 1);
+
+		if ( (up && idx == 0) || (!up && idx == (long) (file->getObjects().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);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDObject::typeName (LDObject::Type type)
+{	LDObject* obj = LDObject::getDefault (type);
+	str name = obj->getTypeName();
+	delete obj;
+	return name;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDObject::describeObjects (const QList<LDObject*>& objs)
+{	bool firstDetails = true;
+	str text = "";
+
+	if (objs.isEmpty())
+		return "nothing"; // :)
+
+	for (long i = 0; i < LDObject::NumTypes; ++i)
+	{	LDObject::Type objType = (LDObject::Type) i;
+		int count = 0;
+
+		for (LDObject * obj : objs)
+			if (obj->getType() == objType)
+				count++;
+
+		if (count == 0)
+			continue;
+
+		if (!firstDetails)
+			text += ", ";
+
+		str noun = fmt ("%1%2", typeName (objType), plural (count));
+
+		// Plural of "vertex" is "vertices", correct that
+		
+		if (objType == LDObject::Vertex && count != 1)
+			noun = "vertices";
+
+		text += fmt ("%1 %2", count, noun);
+		firstDetails = false;
+	}
+
+	return text;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDObject* LDObject::topLevelParent()
+{	if (!getParent())
+		return this;
+
+	LDObject* it = this;
+
+	while (it->getParent())
+		it = it->getParent();
+
+	return it;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDObject* LDObject::next() const
+{	long idx = getIndex();
+	assert (idx != -1);
+
+	if (idx == (long) getFile()->getObjectCount() - 1)
+		return null;
+
+	return getFile()->getObject (idx + 1);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDObject* LDObject::prev() const
+{	long idx = getIndex();
+	assert (idx != -1);
+
+	if (idx == 0)
+		return null;
+
+	return getFile()->getObject (idx - 1);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDObject::move (vertex vect)
+{	if (hasMatrix())
+	{	LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (this);
+		mo->setPosition (mo->getPosition() + vect);
+	}
+	elif (getType() == LDObject::Vertex)
+	{	// ugh
+		static_cast<LDVertex*> (this)->pos += vect;
+	}
+	else
+	{	for (int i = 0; i < vertices(); ++i)
+			setVertex (i, getVertex (i) + vect);
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+#define CHECK_FOR_OBJ(N) \
+	if (type == LDObject::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 = getVertex (1);
+	setVertex (1, getVertex (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 = getVertex (1);
+	setVertex (1, getVertex (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 = getIndex();
+
+	if (idx > 0)
+	{	LDBFC* bfc = dynamic_cast<LDBFC*> (prev());
+
+		if (bfc && bfc->type == LDBFC::InvertNext)
+		{	// This is prefixed with an invertnext, thus remove it.
+			getFile()->forgetObject (bfc);
+			delete bfc;
+			return;
+		}
+	}
+
+	// Not inverted, thus prefix it with a new invertnext.
+	LDBFC* bfc = new LDBFC (LDBFC::InvertNext);
+	getFile()->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->getVertex (0);
+	line->setVertex (0, line->getVertex (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, getVertex (i));
+
+	repl->setColor (getColor());
+
+	replace (repl);
+	return repl;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDObject* LDObject::fromID (int id)
+{	for (LDObject * obj : g_LDObjects)
+		if (obj->getID() == id)
+			return obj;
+
+	return null;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str LDOverlay::raw()
+{	return fmt ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6",
+		getFileName(), getCamera(), getX(), getY(), getWidth(), getHeight());
+}
+
+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->getFile() && (idx = obj->getIndex()) != -1)
+	{	str before = obj->raw();
+		*ptr = val;
+		str after = obj->raw();
+
+		obj->getFile()->addToHistory (new EditHistory (idx, before, after));
+	}
+	else
+		*ptr = val;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDObject::setColor (const int& val)
+{	changeProperty (this, &m_Color, val);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+const vertex& LDObject::getVertex (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 (getLinkPointer(), &m_Position, LDSharedVertex::getSharedVertex (a));
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDMatrixObject::setTransform (const matrix& val)
+{	changeProperty (getLinkPointer(), &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()
+{	if (!getFile())
+	{	log ("Warning: Object #%1 cannot be selected as it is not assigned a file!\n", getID());
+		return;
+	}
+
+	getFile()->addToSelection (this);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDObject::unselect()
+{	if (!getFile())
+	{	log ("Warning: Object #%1 cannot be unselected as it is not assigned a file!\n", getID());
+		return;
+	}
+
+	getFile()->removeFromSelection (this);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str getLicenseText (int id)
+{	switch (id)
+	{	case 0:
+			return CALicense;
+
+		case 1:
+			return NonCALicense;
+
+		case 2:
+			return "";
+	}
+
+	assert (false);
+	return "";
+}
\ No newline at end of file
--- a/src/ldtypes.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,746 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "main.h"
-#include "ldtypes.h"
-#include "document.h"
-#include "misc.h"
-#include "gui.h"
-#include "history.h"
-#include "gldraw.h"
-#include "colors.h"
-
-cfg (String, ld_defaultname, "");
-cfg (String, ld_defaultuser, "");
-cfg (Int, ld_defaultlicense, 0);
-
-// List of all LDObjects
-QList<LDObject*> g_LDObjects;
-
-// =============================================================================
-// LDObject constructors
-// -----------------------------------------------------------------------------
-LDObject::LDObject() :
-	m_Hidden (false),
-	m_Selected (false),
-	m_Parent (null),
-	m_File (null),
-	qObjListEntry (null),
-	m_glinit (false)
-{
-	memset (m_coords, 0, sizeof m_coords);
-
-	// Determine ID
-	int32 id = 1; // 0 is invalid
-
-	for (LDObject* obj : g_LDObjects)
-		if (obj->getID() >= id)
-			id = obj->getID() + 1;
-
-	setID (id);
-	g_LDObjects << this;
-}
-
-// =============================================================================
-// Default implementations for LDObject's virtual methods. These should never be
-// actually called, for a subclass-less LDObject should never come into existance.
-// These exist only to satisfy the linker.
-// -----------------------------------------------------------------------------
-LDObject::Type LDObject::getType() const
-{	return LDObject::Unidentified;
-}
-
-bool LDObject::hasMatrix() const
-{	return false;
-}
-
-bool LDObject::isColored() const
-{	return false;
-}
-
-bool LDObject::isScemantic() const
-{	return false;
-}
-
-str LDObject::getTypeName() const
-{	return "";
-}
-
-int LDObject::vertices() const
-{	return 0;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDObject::setVertexCoord (int i, Axis ax, double value)
-{	vertex v = getVertex (i);
-	v[ax] = value;
-	setVertex (i, v);
-}
-
-LDError::LDError() {}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDComment::raw()
-{	return fmt ("0 %1", text);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDSubfile::raw()
-{	str val = fmt ("1 %1 %2 ", getColor(), getPosition());
-	val += getTransform().stringRep();
-	val += ' ';
-	val += getFileInfo()->getName();
-	return val;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDLine::raw()
-{	str val = fmt ("2 %1", getColor());
-
-	for (int i = 0; i < 2; ++i)
-		val += fmt (" %1", getVertex (i));
-
-	return val;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDTriangle::raw()
-{	str val = fmt ("3 %1", getColor());
-
-	for (int i = 0; i < 3; ++i)
-		val += fmt (" %1", getVertex (i));
-
-	return val;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDQuad::raw()
-{	str val = fmt ("4 %1", getColor());
-
-	for (int i = 0; i < 4; ++i)
-		val += fmt (" %1", getVertex (i));
-
-	return val;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDCondLine::raw()
-{	str val = fmt ("5 %1", getColor());
-
-	// Add the coordinates
-	for (int i = 0; i < 4; ++i)
-		val += fmt (" %1", getVertex (i));
-
-	return val;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDError::raw()
-{	return contents;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDVertex::raw()
-{	return fmt ("0 !LDFORGE VERTEX %1 %2", getColor(), pos);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDEmpty::raw()
-{	return "";
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-const char* LDBFC::statements[] =
-{	"CERTIFY CCW",
-	"CCW",
-	"CERTIFY CW",
-	"CW",
-	"NOCERTIFY",
-	"INVERTNEXT",
-	"CLIP",
-	"CLIP CCW",
-	"CLIP CW",
-	"NOCLIP",
-};
-
-str LDBFC::raw()
-{	return fmt ("0 BFC %1", LDBFC::statements[type]);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-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 (getVertex (0), getVertex (1), getVertex (3));
-	LDTriangle* tri2 = new LDTriangle (getVertex (1), getVertex (2), getVertex (3));
-
-	// The triangles also inherit the quad's color
-	tri1->setColor (getColor());
-	tri2->setColor (getColor());
-
-	QList<LDTriangle*> triangles;
-	triangles << tri1;
-	triangles << tri2;
-	return triangles;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDObject::replace (LDObject* other)
-{	long idx = getIndex();
-	assert (idx != -1);
-
-	// Replace the instance of the old object with the new object
-	getFile()->setObject (idx, other);
-
-	// Remove the old object
-	delete this;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDObject::swap (LDObject* other)
-{	int i = 0;
-
-	for (LDObject* obj : getFile()->getObjects())
-	{	if (obj == this)
-			getFile()->setObject (i, other);
-		elif (obj == other)
-			getFile()->setObject (i, this);
-
-		++i;
-	}
-
-	getFile()->addToHistory (new SwapHistory (getID(), other->getID()));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-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()
-{	// 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 (getFile())
-		getFile()->forgetObject (this);
-
-	// Delete the GL lists
-	GL::deleteLists (this);
-
-	// Remove this object from the list of LDObjects
-	g_LDObjects.removeOne (this);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static void transformObject (LDObject* obj, matrix transform, vertex pos, int parentcolor)
-{	switch (obj->getType())
-	{	case LDObject::Line:
-		case LDObject::CondLine:
-		case LDObject::Triangle:
-		case LDObject::Quad:
-
-			for (int i = 0; i < obj->vertices(); ++i)
-			{	vertex v = obj->getVertex (i);
-				v.transform (transform, pos);
-				obj->setVertex (i, v);
-			}
-
-			break;
-
-		case LDObject::Subfile:
-		{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			matrix newMatrix = transform * ref->getTransform();
-			vertex newpos = ref->getPosition();
-
-			newpos.transform (transform, pos);
-			ref->setPosition (newpos);
-			ref->setTransform (newMatrix);
-		}
-		break;
-
-		default:
-			break;
-	}
-
-	if (obj->getColor() == maincolor)
-		obj->setColor (parentcolor);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QList<LDObject*> LDSubfile::inlineContents (InlineFlags flags)
-{	QList<LDObject*> objs = getFileInfo()->inlineContents (flags);
-
-	// Transform the objects
-for (LDObject * obj : objs)
-	{	// Set the parent now so we know what inlined this.
-		obj->setParent (this);
-		transformObject (obj, getTransform(), getPosition(), getColor());
-	}
-
-	return objs;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-long LDObject::getIndex() const
-{
-#ifndef RELEASE
-	assert (getFile() != null);
-#endif
-
-	for (int i = 0; i < getFile()->getObjectCount(); ++i)
-		if (getFile()->getObject (i) == this)
-			return i;
-
-	return -1;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDObject::moveObjects (QList<LDObject*> 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;
-	QList<LDObject*> objsToCompile;
-	LDDocument* file = objs[0]->getFile();
-
-	for (long i = start; i != end; i += incr)
-	{	LDObject* obj = objs[i];
-
-		const long idx = obj->getIndex(),
-				   target = idx + (up ? -1 : 1);
-
-		if ( (up && idx == 0) || (!up && idx == (long) (file->getObjects().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);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDObject::typeName (LDObject::Type type)
-{	LDObject* obj = LDObject::getDefault (type);
-	str name = obj->getTypeName();
-	delete obj;
-	return name;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDObject::describeObjects (const QList<LDObject*>& objs)
-{	bool firstDetails = true;
-	str text = "";
-
-	if (objs.isEmpty())
-		return "nothing"; // :)
-
-	for (long i = 0; i < LDObject::NumTypes; ++i)
-	{	LDObject::Type objType = (LDObject::Type) i;
-		int count = 0;
-
-		for (LDObject * obj : objs)
-			if (obj->getType() == objType)
-				count++;
-
-		if (count == 0)
-			continue;
-
-		if (!firstDetails)
-			text += ", ";
-
-		str noun = fmt ("%1%2", typeName (objType), plural (count));
-
-		// Plural of "vertex" is "vertices", correct that
-		
-		if (objType == LDObject::Vertex && count != 1)
-			noun = "vertices";
-
-		text += fmt ("%1 %2", count, noun);
-		firstDetails = false;
-	}
-
-	return text;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObject* LDObject::topLevelParent()
-{	if (!getParent())
-		return this;
-
-	LDObject* it = this;
-
-	while (it->getParent())
-		it = it->getParent();
-
-	return it;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObject* LDObject::next() const
-{	long idx = getIndex();
-	assert (idx != -1);
-
-	if (idx == (long) getFile()->getObjectCount() - 1)
-		return null;
-
-	return getFile()->getObject (idx + 1);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObject* LDObject::prev() const
-{	long idx = getIndex();
-	assert (idx != -1);
-
-	if (idx == 0)
-		return null;
-
-	return getFile()->getObject (idx - 1);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDObject::move (vertex vect)
-{	if (hasMatrix())
-	{	LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (this);
-		mo->setPosition (mo->getPosition() + vect);
-	}
-	elif (getType() == LDObject::Vertex)
-	{	// ugh
-		static_cast<LDVertex*> (this)->pos += vect;
-	}
-	else
-	{	for (int i = 0; i < vertices(); ++i)
-			setVertex (i, getVertex (i) + vect);
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-#define CHECK_FOR_OBJ(N) \
-	if (type == LDObject::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 = getVertex (1);
-	setVertex (1, getVertex (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 = getVertex (1);
-	setVertex (1, getVertex (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 = getIndex();
-
-	if (idx > 0)
-	{	LDBFC* bfc = dynamic_cast<LDBFC*> (prev());
-
-		if (bfc && bfc->type == LDBFC::InvertNext)
-		{	// This is prefixed with an invertnext, thus remove it.
-			getFile()->forgetObject (bfc);
-			delete bfc;
-			return;
-		}
-	}
-
-	// Not inverted, thus prefix it with a new invertnext.
-	LDBFC* bfc = new LDBFC (LDBFC::InvertNext);
-	getFile()->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->getVertex (0);
-	line->setVertex (0, line->getVertex (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, getVertex (i));
-
-	repl->setColor (getColor());
-
-	replace (repl);
-	return repl;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObject* LDObject::fromID (int id)
-{	for (LDObject * obj : g_LDObjects)
-		if (obj->getID() == id)
-			return obj;
-
-	return null;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str LDOverlay::raw()
-{	return fmt ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6",
-		getFileName(), getCamera(), getX(), getY(), getWidth(), getHeight());
-}
-
-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->getFile() && (idx = obj->getIndex()) != -1)
-	{	str before = obj->raw();
-		*ptr = val;
-		str after = obj->raw();
-
-		obj->getFile()->addToHistory (new EditHistory (idx, before, after));
-	}
-	else
-		*ptr = val;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDObject::setColor (const int& val)
-{	changeProperty (this, &m_Color, val);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-const vertex& LDObject::getVertex (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 (getLinkPointer(), &m_Position, LDSharedVertex::getSharedVertex (a));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDMatrixObject::setTransform (const matrix& val)
-{	changeProperty (getLinkPointer(), &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()
-{	if (!getFile())
-	{	log ("Warning: Object #%1 cannot be selected as it is not assigned a file!\n", getID());
-		return;
-	}
-
-	getFile()->addToSelection (this);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDObject::unselect()
-{	if (!getFile())
-	{	log ("Warning: Object #%1 cannot be unselected as it is not assigned a file!\n", getID());
-		return;
-	}
-
-	getFile()->removeFromSelection (this);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str getLicenseText (int id)
-{	switch (id)
-	{	case 0:
-			return CALicense;
-
-		case 1:
-			return NonCALicense;
-
-		case 2:
-			return "";
-	}
-
-	assert (false);
-	return "";
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,141 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QApplication>
+#include <QMessageBox>
+#include <QAbstractButton>
+#include <QFile>
+#include <QTextStream>
+#include "gui.h"
+#include "document.h"
+#include "misc.h"
+#include "config.h"
+#include "colors.h"
+#include "types.h"
+#include "primitives.h"
+#include "gldraw.h"
+#include "configDialog.h"
+#include "dialogs.h"
+#include "crashcatcher.h"
+
+QList<LDDocument*> g_loadedFiles;
+ForgeWindow* g_win = null;
+const QApplication* g_app = null;
+File g_file_stdout (stdout, File::Write);
+File g_file_stderr (stderr, File::Write);
+static str 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);
+	g_app = &app;
+
+	initCrashCatcher();
+	LDDocument::setCurrent (null);
+
+	// Load or create the configuration
+	if (!Config::load())
+	{	log ("Creating configuration file...\n");
+
+		if (Config::save())
+			log ("Configuration file successfully created.\n");
+		else
+			log ("failed to create configuration file!\n");
+	}
+
+	LDPaths::initPaths();
+	initColors();
+	loadLogoedStuds();
+
+	ForgeWindow* win = new ForgeWindow;
+	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();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void doPrint (File& f, initlist<StringFormatArg> args)
+{	str msg = DoFormat (args);
+	f.write (msg.toUtf8());
+	f.flush();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void doPrint (FILE* fp, initlist<StringFormatArg> args)
+{	str msg = DoFormat (args);
+	fwrite (msg.toStdString().c_str(), 1, msg.length(), fp);
+	fflush (fp);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+QString versionString()
+{	if (g_versionString.length() == 0)
+	{
+#if VERSION_PATCH == 0
+		g_versionString = fmt ("%1.%2", VERSION_MAJOR, VERSION_MINOR);
+#else
+		g_versionString = fmt ("%1.%2.%3", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
+#endif // VERSION_PATCH
+	}
+
+	return g_versionString;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+QString versionMoniker()
+{
+#if BUILD_ID == BUILD_INTERNAL
+	return "Internal";
+#elif BUILD_ID == BUILD_ALPHA
+	return "Alpha";
+#elif BUILD_ID == BUILD_BETA
+	return "Beta";
+#elif BUILD_ID == BUILD_RC
+	return fmt ("RC %1", RC_NUMBER);
+#else
+	return "";
+#endif // BUILD_ID
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+QString fullVersionString()
+{	return fmt ("v%1 %2", versionString(), versionMoniker());
+}
\ No newline at end of file
--- a/src/main.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QApplication>
-#include <QMessageBox>
-#include <QAbstractButton>
-#include <QFile>
-#include <QTextStream>
-#include "gui.h"
-#include "document.h"
-#include "misc.h"
-#include "config.h"
-#include "colors.h"
-#include "types.h"
-#include "primitives.h"
-#include "gldraw.h"
-#include "configDialog.h"
-#include "dialogs.h"
-#include "crashcatcher.h"
-
-QList<LDDocument*> g_loadedFiles;
-ForgeWindow* g_win = null;
-const QApplication* g_app = null;
-File g_file_stdout (stdout, File::Write);
-File g_file_stderr (stderr, File::Write);
-static str 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);
-	g_app = &app;
-
-	initCrashCatcher();
-	LDDocument::setCurrent (null);
-
-	// Load or create the configuration
-	if (!Config::load())
-	{	log ("Creating configuration file...\n");
-
-		if (Config::save())
-			log ("Configuration file successfully created.\n");
-		else
-			log ("failed to create configuration file!\n");
-	}
-
-	LDPaths::initPaths();
-	initColors();
-	loadLogoedStuds();
-
-	ForgeWindow* win = new ForgeWindow;
-	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();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void doPrint (File& f, initlist<StringFormatArg> args)
-{	str msg = DoFormat (args);
-	f.write (msg.toUtf8());
-	f.flush();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void doPrint (FILE* fp, initlist<StringFormatArg> args)
-{	str msg = DoFormat (args);
-	fwrite (msg.toStdString().c_str(), 1, msg.length(), fp);
-	fflush (fp);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QString versionString()
-{	if (g_versionString.length() == 0)
-	{
-#if VERSION_PATCH == 0
-		g_versionString = fmt ("%1.%2", VERSION_MAJOR, VERSION_MINOR);
-#else
-		g_versionString = fmt ("%1.%2.%3", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
-#endif // VERSION_PATCH
-	}
-
-	return g_versionString;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QString versionMoniker()
-{
-#if BUILD_ID == BUILD_INTERNAL
-	return "Internal";
-#elif BUILD_ID == BUILD_ALPHA
-	return "Alpha";
-#elif BUILD_ID == BUILD_BETA
-	return "Beta";
-#elif BUILD_ID == BUILD_RC
-	return fmt ("RC %1", RC_NUMBER);
-#else
-	return "";
-#endif // BUILD_ID
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QString fullVersionString()
-{	return fmt ("v%1 %2", versionString(), versionMoniker());
-}
\ No newline at end of file
--- a/src/main.h	Fri Dec 13 00:39:49 2013 +0200
+++ b/src/main.h	Fri Dec 13 20:01:49 2013 +0200
@@ -122,6 +122,7 @@
 #define properties private
 #define typedefs public
 #define methods
+#define for_axes(AX) for (const Axis ax : std::initializer_list<const Axis> ({X, Y, Z}))
 
 // -----------------------------------------------------------------------------
 #ifdef IN_IDE_PARSER // KDevelop workarounds:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/messagelog.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,129 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QTimer>
+#include <QDate>
+#include "messagelog.h"
+#include "gldraw.h"
+#include "gui.h"
+#include "moc_messagelog.cpp"
+
+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 (str text) :
+			text (text),
+			alpha (1.0f),
+			expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {}
+
+// =============================================================================
+// Check this line's expiry and update alpha accordingly. Returns true if the
+// line is to still stick around, false if it expired. 'changed' is updated to
+// whether the line has somehow changed since the last update.
+// -----------------------------------------------------------------------------
+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 (str 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 (getRenderer())
+		getRenderer()->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 && getRenderer())
+		getRenderer()->update();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+const QList<MessageManager::Line>& MessageManager::getLines() const
+{	return m_lines;
+}
+
+// =============================================================================
+// log() interface - format the argument list and add the resulting string to
+// the main message manager.
+// -----------------------------------------------------------------------------
+void DoLog (std::initializer_list<StringFormatArg> args)
+{	const str msg = DoFormat (args);
+
+	for (str& a : msg.split ("\n", QString::SkipEmptyParts))
+	{	if (g_win)
+			g_win->addMessage (a);
+
+		// Also print it to stdout
+		fprint (stdout, "%1\n", a);
+	}
+}
--- a/src/messagelog.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QTimer>
-#include <QDate>
-#include "messagelog.h"
-#include "gldraw.h"
-#include "gui.h"
-#include "moc_messagelog.cpp"
-
-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 (str text) :
-			text (text),
-			alpha (1.0f),
-			expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {}
-
-// =============================================================================
-// Check this line's expiry and update alpha accordingly. Returns true if the
-// line is to still stick around, false if it expired. 'changed' is updated to
-// whether the line has somehow changed since the last update.
-// -----------------------------------------------------------------------------
-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 (str 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 (getRenderer())
-		getRenderer()->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 && getRenderer())
-		getRenderer()->update();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-const QList<MessageManager::Line>& MessageManager::getLines() const
-{	return m_lines;
-}
-
-// =============================================================================
-// log() interface - format the argument list and add the resulting string to
-// the main message manager.
-// -----------------------------------------------------------------------------
-void DoLog (std::initializer_list<StringFormatArg> args)
-{	const str msg = DoFormat (args);
-
-	for (str& a : msg.split ("\n", QString::SkipEmptyParts))
-	{	if (g_win)
-			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/misc.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,428 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+#include <locale.h>
+#include <QColor>
+#include "main.h"
+#include "misc.h"
+#include "gui.h"
+#include "dialogs.h"
+#include "ui_rotpoint.h"
+
+RingFinder g_RingFinder;
+
+// 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 int32 g_e10[] =
+{	1,
+	10,
+	100,
+	1000,
+	10000,
+	100000,
+	1000000,
+	10000000,
+	100000000,
+	1000000000,
+};
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+// 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 (Float, edit_rotpoint_x, 0.0f); // TODO: make a VertexConfig and use it here
+cfg (Float, edit_rotpoint_y, 0.0f);
+cfg (Float, edit_rotpoint_z, 0.0f);
+
+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]->value;
+	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 str& 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 QList<LDObject*>& objs)
+{	LDBoundingBox box;
+
+	switch (edit_rotpoint)
+	{	case ObjectOrigin:
+		{	// Calculate center vertex
+			for (LDObject* obj : objs)
+				if (obj->hasMatrix())
+					box << dynamic_cast<LDMatrixObject*> (obj)->getPosition();
+				else
+					box << obj;
+
+			return box.center();
+		}
+
+		case WorldOrigin:
+		{	return g_origin;
+		}
+
+		case CustomPoint:
+		{	return vertex (edit_rotpoint_x, edit_rotpoint_y, edit_rotpoint_z);
+		}
+	}
+
+	return vertex();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void configRotationPoint()
+{	QDialog* dlg = new QDialog;
+	Ui::RotPointUI ui;
+	ui.setupUi (dlg);
+
+	switch (edit_rotpoint)
+	{	case ObjectOrigin:
+			ui.objectPoint->setChecked (true);
+			break;
+
+		case WorldOrigin:
+			ui.worldPoint->setChecked (true);
+			break;
+
+		case CustomPoint:
+			ui.customPoint->setChecked (true);
+			break;
+	}
+
+	ui.customX->setValue (edit_rotpoint_x);
+	ui.customY->setValue (edit_rotpoint_y);
+	ui.customZ->setValue (edit_rotpoint_z);
+
+	if (!dlg->exec())
+		return;
+
+	edit_rotpoint =
+		(ui.objectPoint->isChecked()) ? ObjectOrigin :
+		(ui.worldPoint->isChecked())  ? WorldOrigin :
+		CustomPoint;
+
+	edit_rotpoint_x = ui.customX->value();
+	edit_rotpoint_y = ui.customY->value();
+	edit_rotpoint_z = ui.customZ->value();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str join (initlist<StringFormatArg> vals, str delim)
+{	QStringList list;
+
+	for (const StringFormatArg& arg : vals)
+		list << arg.value();
+
+	return list.join (delim);
+}
+
+// =============================================================================
+// 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.
+// -----------------------------------------------------------------------------
+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 > *m_bestSolution)
+			m_bestSolution = &sol;
+	}
+
+	return (m_bestSolution != null);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool RingFinder::Solution::operator> (const RingFinder::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)
+	{	if (getComponents()[i].num > maxA)
+			maxA = getComponents()[i].num;
+
+		if (other.getComponents()[i].num > maxB)
+			maxB = other.getComponents()[i].num;
+	}
+
+	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;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+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.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,428 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <math.h>
-#include <locale.h>
-#include <QColor>
-#include "main.h"
-#include "misc.h"
-#include "gui.h"
-#include "dialogs.h"
-#include "ui_rotpoint.h"
-
-RingFinder g_RingFinder;
-
-// 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 int32 g_e10[] =
-{	1,
-	10,
-	100,
-	1000,
-	10000,
-	100000,
-	1000000,
-	10000000,
-	100000000,
-	1000000000,
-};
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-// 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 (Float, edit_rotpoint_x, 0.0f); // TODO: make a VertexConfig and use it here
-cfg (Float, edit_rotpoint_y, 0.0f);
-cfg (Float, edit_rotpoint_z, 0.0f);
-
-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]->value;
-	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 str& 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 QList<LDObject*>& objs)
-{	LDBoundingBox box;
-
-	switch (edit_rotpoint)
-	{	case ObjectOrigin:
-		{	// Calculate center vertex
-			for (LDObject* obj : objs)
-				if (obj->hasMatrix())
-					box << dynamic_cast<LDMatrixObject*> (obj)->getPosition();
-				else
-					box << obj;
-
-			return box.center();
-		}
-
-		case WorldOrigin:
-		{	return g_origin;
-		}
-
-		case CustomPoint:
-		{	return vertex (edit_rotpoint_x, edit_rotpoint_y, edit_rotpoint_z);
-		}
-	}
-
-	return vertex();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void configRotationPoint()
-{	QDialog* dlg = new QDialog;
-	Ui::RotPointUI ui;
-	ui.setupUi (dlg);
-
-	switch (edit_rotpoint)
-	{	case ObjectOrigin:
-			ui.objectPoint->setChecked (true);
-			break;
-
-		case WorldOrigin:
-			ui.worldPoint->setChecked (true);
-			break;
-
-		case CustomPoint:
-			ui.customPoint->setChecked (true);
-			break;
-	}
-
-	ui.customX->setValue (edit_rotpoint_x);
-	ui.customY->setValue (edit_rotpoint_y);
-	ui.customZ->setValue (edit_rotpoint_z);
-
-	if (!dlg->exec())
-		return;
-
-	edit_rotpoint =
-		(ui.objectPoint->isChecked()) ? ObjectOrigin :
-		(ui.worldPoint->isChecked())  ? WorldOrigin :
-		CustomPoint;
-
-	edit_rotpoint_x = ui.customX->value();
-	edit_rotpoint_y = ui.customY->value();
-	edit_rotpoint_z = ui.customZ->value();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str join (initlist<StringFormatArg> vals, str delim)
-{	QStringList list;
-
-	for (const StringFormatArg& arg : vals)
-		list << arg.value();
-
-	return list.join (delim);
-}
-
-// =============================================================================
-// 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.
-// -----------------------------------------------------------------------------
-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 > *m_bestSolution)
-			m_bestSolution = &sol;
-	}
-
-	return (m_bestSolution != null);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool RingFinder::Solution::operator> (const RingFinder::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)
-	{	if (getComponents()[i].num > maxA)
-			maxA = getComponents()[i].num;
-
-		if (other.getComponents()[i].num > maxB)
-			maxB = other.getComponents()[i].num;
-	}
-
-	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;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/primitives.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,631 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDir>
+#include <QRegExp>
+#include <QFileDialog>
+#include "document.h"
+#include "gui.h"
+#include "primitives.h"
+#include "ui_makeprim.h"
+#include "misc.h"
+#include "colors.h"
+#include "moc_primitives.cpp"
+
+QList<PrimitiveCategory*> g_PrimitiveCategories;
+QList<Primitive> g_primitives;
+static PrimitiveLister* g_activePrimLister = null;
+PrimitiveCategory* g_unmatched = null;
+
+extern_cfg (String, ld_defaultname);
+extern_cfg (String, ld_defaultuser);
+extern_cfg (Int, ld_defaultlicense);
+
+static const str g_radialNameRoots[] =
+{	"edge",
+	"cyli",
+	"disc",
+	"ndis",
+	"ring",
+	"con"
+};
+
+PrimitiveLister* getPrimitiveLister()
+{	return g_activePrimLister;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void loadPrimitives()
+{	log ("Loading primitives...\n");
+	PrimitiveCategory::loadCategories();
+
+	// Try to load prims.cfg
+	File conf (Config::filepath ("prims.cfg"), File::Read);
+
+	if (!conf)
+	{	// No prims.cfg, build it
+		PrimitiveLister::start();
+	}
+	else
+	{	// Read primitives from prims.cfg
+		for (str line : conf)
+		{	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();
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static void recursiveGetFilenames (QDir dir, QList<str>& fnames)
+{	QFileInfoList flist = dir.entryInfoList();
+
+	for (const QFileInfo & info : flist)
+	{	if (info.fileName() == "." || info.fileName() == "..")
+			continue; // skip . and ..
+
+		if (info.isDir())
+			recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames);
+		else
+			fnames << info.absoluteFilePath();
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+PrimitiveLister::PrimitiveLister (QObject* parent) :
+	QObject (parent),
+	m_i (0)
+{	g_activePrimLister = this;
+	QDir dir (LDPaths::prims());
+	assert (dir.exists());
+	m_baselen = dir.absolutePath().length();
+	recursiveGetFilenames (dir, m_files);
+	emit starting (m_files.size());
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+PrimitiveLister::~PrimitiveLister()
+{	g_activePrimLister = null;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PrimitiveLister::work()
+{	int j = min (m_i + 300, m_files.size());
+	log ("PrimitiveLister::work: %1 -> %2\n", m_i, j);
+
+	for (; m_i < j; ++m_i)
+	{	str fname = m_files[m_i];
+		File f (fname, File::Read);
+		Primitive info;
+		info.name = fname.mid (m_baselen + 1);  // make full path relative
+		info.name.replace ('/', '\\');  // use DOS backslashes, they're expected
+		info.cat = null;
+
+		if (!f.readLine (info.title))
+			info.title = "";
+
+		info.title = info.title.simplified();
+
+		if (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
+		File conf (Config::filepath ("prims.cfg"), File::Write);
+
+		for (Primitive& info : m_prims)
+			fprint (conf, "%1 %2\n", info.name, info.title);
+
+		conf.close();
+
+		g_primitives = m_prims;
+		PrimitiveCategory::populateCategories();
+		log ("%1 primitives listed", g_primitives.size());
+		g_activePrimLister = 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 PrimitiveLister::start()
+{	if (g_activePrimLister)
+		return;
+
+	PrimitiveLister* lister = new PrimitiveLister;
+	/*
+	connect (lister, SIGNAL (starting (int)), g_win, SLOT (primitiveLoaderStart (int)));
+	connect (lister, SIGNAL (update (int)), g_win, SLOT (primitiveLoaderUpdate (int)));
+	connect (lister, SIGNAL (workDone()), g_win, SLOT (primitiveLoaderEnd()));
+	*/
+	lister->work();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+PrimitiveCategory::PrimitiveCategory (str 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.cat = 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.cat = cat;
+					break;
+				}
+			}
+
+			// Drop out if a category was decided on.
+			if (prim.cat != null)
+				break;
+		}
+
+		// If there was a match, add the primitive to the category.
+		// Otherwise, add it to the list of unmatched primitives.
+		if (prim.cat != null)
+			prim.cat->prims << prim;
+		else
+			g_unmatched->prims << prim;
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void PrimitiveCategory::loadCategories()
+{	for (PrimitiveCategory* cat : g_PrimitiveCategories)
+		delete cat;
+
+	g_PrimitiveCategories.clear();
+	File f (Config::dirpath() + "primregexps.cfg", File::Read);
+
+	if (!f)
+		f.open (":/data/primitive-categories.cfg", File::Read);
+
+	if (!f)
+	{	critical (QObject::tr ("Failed to open primitive categories!"));
+		return;
+	}
+
+	if (f)
+	{	PrimitiveCategory* cat = null;
+
+		for (str line : f)
+		{	int colon;
+
+			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)
+			{	str cmd = line.left (colon);
+				ERegexType type = EFilenameRegex;
+
+				if (cmd == "f")
+					type = EFilenameRegex;
+				elif (cmd == "t")
+					type = ETitleRegex;
+				else
+				{	log (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
+				log ("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;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool PrimitiveCategory::isValidToInclude()
+{	if (regexes.size() == 0)
+	{	log (tr ("Warning: category \"%1\" left without patterns"), getName());
+		deleteLater();
+		return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool isPrimitiveLoaderBusy()
+{	return g_activePrimLister != 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));
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+QList<LDObject*> makePrimitive (PrimitiveType type, int segs, int divs, int num)
+{	QList<LDObject*> 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 str 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";
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str 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
+	str prefix = (divs == lores) ? "" : fmt ("%1/", divs);
+	str frac = fmt ("%1-%2", numer, denom);
+	str root = g_radialNameRoots[type];
+	str numstr = (type == Ring || type == Cone) ? fmt ("%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
+	str frac = str::number ((float) segs / divs);
+	str name = radialFileName (type, segs, divs, num);
+	str descr;
+
+	// Ensure that there's decimals, even if they're 0.
+	if (frac.indexOf (".") == -1)
+		frac += ".0";
+
+	if (type == Ring || type == Cone)
+	{	str spacing =
+			(num < 10) ? "  " :
+			(num < 100) ? " "  : "";
+
+		descr = fmt ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac);
+	}
+	else
+		descr = fmt ("%1 %2", primitiveTypeName (type), frac);
+
+	// Prepend "Hi-Res" if 48/ primitive.
+	if (divs == hires)
+		descr.insert (0, "Hi-Res ");
+
+	LDDocument* f = new LDDocument;
+	f->setDefaultName (name);
+
+	str author = APPNAME;
+	str license = "";
+
+	if (ld_defaultname.value.isEmpty() == false)
+	{	license = getLicenseText (ld_defaultlicense);
+		author = fmt ("%1 [%2]", ld_defaultname, ld_defaultuser);
+	}
+
+	f->addObjects (
+	{	new LDComment (descr),
+		new LDComment (fmt ("Name: %1", name)),
+		new LDComment (fmt ("Author: %1", author)),
+		new LDComment (fmt ("!LDRAW_ORG Unofficial_%1Primitive", divs == 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)
+{	str 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 ? hires : lores);
+
+	// If the current value is 16 and we switch to hi-res, default the
+	// spinbox to 48.
+	if (on && ui->sb_segs->value() == lores)
+		ui->sb_segs->setValue (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() ? hires : 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.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,631 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDir>
-#include <QRegExp>
-#include <QFileDialog>
-#include "document.h"
-#include "gui.h"
-#include "primitives.h"
-#include "ui_makeprim.h"
-#include "misc.h"
-#include "colors.h"
-#include "moc_primitives.cpp"
-
-QList<PrimitiveCategory*> g_PrimitiveCategories;
-QList<Primitive> g_primitives;
-static PrimitiveLister* g_activePrimLister = null;
-PrimitiveCategory* g_unmatched = null;
-
-extern_cfg (String, ld_defaultname);
-extern_cfg (String, ld_defaultuser);
-extern_cfg (Int, ld_defaultlicense);
-
-static const str g_radialNameRoots[] =
-{	"edge",
-	"cyli",
-	"disc",
-	"ndis",
-	"ring",
-	"con"
-};
-
-PrimitiveLister* getPrimitiveLister()
-{	return g_activePrimLister;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void loadPrimitives()
-{	log ("Loading primitives...\n");
-	PrimitiveCategory::loadCategories();
-
-	// Try to load prims.cfg
-	File conf (Config::filepath ("prims.cfg"), File::Read);
-
-	if (!conf)
-	{	// No prims.cfg, build it
-		PrimitiveLister::start();
-	}
-	else
-	{	// Read primitives from prims.cfg
-		for (str line : conf)
-		{	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();
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-static void recursiveGetFilenames (QDir dir, QList<str>& fnames)
-{	QFileInfoList flist = dir.entryInfoList();
-
-	for (const QFileInfo & info : flist)
-	{	if (info.fileName() == "." || info.fileName() == "..")
-			continue; // skip . and ..
-
-		if (info.isDir())
-			recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames);
-		else
-			fnames << info.absoluteFilePath();
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-PrimitiveLister::PrimitiveLister (QObject* parent) :
-	QObject (parent),
-	m_i (0)
-{	g_activePrimLister = this;
-	QDir dir (LDPaths::prims());
-	assert (dir.exists());
-	m_baselen = dir.absolutePath().length();
-	recursiveGetFilenames (dir, m_files);
-	emit starting (m_files.size());
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-PrimitiveLister::~PrimitiveLister()
-{	g_activePrimLister = null;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PrimitiveLister::work()
-{	int j = min (m_i + 300, m_files.size());
-	log ("PrimitiveLister::work: %1 -> %2\n", m_i, j);
-
-	for (; m_i < j; ++m_i)
-	{	str fname = m_files[m_i];
-		File f (fname, File::Read);
-		Primitive info;
-		info.name = fname.mid (m_baselen + 1);  // make full path relative
-		info.name.replace ('/', '\\');  // use DOS backslashes, they're expected
-		info.cat = null;
-
-		if (!f.readLine (info.title))
-			info.title = "";
-
-		info.title = info.title.simplified();
-
-		if (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
-		File conf (Config::filepath ("prims.cfg"), File::Write);
-
-		for (Primitive& info : m_prims)
-			fprint (conf, "%1 %2\n", info.name, info.title);
-
-		conf.close();
-
-		g_primitives = m_prims;
-		PrimitiveCategory::populateCategories();
-		log ("%1 primitives listed", g_primitives.size());
-		g_activePrimLister = 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 PrimitiveLister::start()
-{	if (g_activePrimLister)
-		return;
-
-	PrimitiveLister* lister = new PrimitiveLister;
-	/*
-	connect (lister, SIGNAL (starting (int)), g_win, SLOT (primitiveLoaderStart (int)));
-	connect (lister, SIGNAL (update (int)), g_win, SLOT (primitiveLoaderUpdate (int)));
-	connect (lister, SIGNAL (workDone()), g_win, SLOT (primitiveLoaderEnd()));
-	*/
-	lister->work();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-PrimitiveCategory::PrimitiveCategory (str 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.cat = 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.cat = cat;
-					break;
-				}
-			}
-
-			// Drop out if a category was decided on.
-			if (prim.cat != null)
-				break;
-		}
-
-		// If there was a match, add the primitive to the category.
-		// Otherwise, add it to the list of unmatched primitives.
-		if (prim.cat != null)
-			prim.cat->prims << prim;
-		else
-			g_unmatched->prims << prim;
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void PrimitiveCategory::loadCategories()
-{	for (PrimitiveCategory* cat : g_PrimitiveCategories)
-		delete cat;
-
-	g_PrimitiveCategories.clear();
-	File f (Config::dirpath() + "primregexps.cfg", File::Read);
-
-	if (!f)
-		f.open (":/data/primitive-categories.cfg", File::Read);
-
-	if (!f)
-	{	critical (QObject::tr ("Failed to open primitive categories!"));
-		return;
-	}
-
-	if (f)
-	{	PrimitiveCategory* cat = null;
-
-		for (str line : f)
-		{	int colon;
-
-			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)
-			{	str cmd = line.left (colon);
-				ERegexType type = EFilenameRegex;
-
-				if (cmd == "f")
-					type = EFilenameRegex;
-				elif (cmd == "t")
-					type = ETitleRegex;
-				else
-				{	log (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
-				log ("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;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool PrimitiveCategory::isValidToInclude()
-{	if (regexes.size() == 0)
-	{	log (tr ("Warning: category \"%1\" left without patterns"), getName());
-		deleteLater();
-		return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool isPrimitiveLoaderBusy()
-{	return g_activePrimLister != 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));
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-QList<LDObject*> makePrimitive (PrimitiveType type, int segs, int divs, int num)
-{	QList<LDObject*> 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 str 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";
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str 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
-	str prefix = (divs == lores) ? "" : fmt ("%1/", divs);
-	str frac = fmt ("%1-%2", numer, denom);
-	str root = g_radialNameRoots[type];
-	str numstr = (type == Ring || type == Cone) ? fmt ("%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
-	str frac = str::number ((float) segs / divs);
-	str name = radialFileName (type, segs, divs, num);
-	str descr;
-
-	// Ensure that there's decimals, even if they're 0.
-	if (frac.indexOf (".") == -1)
-		frac += ".0";
-
-	if (type == Ring || type == Cone)
-	{	str spacing =
-			(num < 10) ? "  " :
-			(num < 100) ? " "  : "";
-
-		descr = fmt ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac);
-	}
-	else
-		descr = fmt ("%1 %2", primitiveTypeName (type), frac);
-
-	// Prepend "Hi-Res" if 48/ primitive.
-	if (divs == hires)
-		descr.insert (0, "Hi-Res ");
-
-	LDDocument* f = new LDDocument;
-	f->setDefaultName (name);
-
-	str author = APPNAME;
-	str license = "";
-
-	if (ld_defaultname.value.isEmpty() == false)
-	{	license = getLicenseText (ld_defaultlicense);
-		author = fmt ("%1 [%2]", ld_defaultname, ld_defaultuser);
-	}
-
-	f->addObjects (
-	{	new LDComment (descr),
-		new LDComment (fmt ("Name: %1", name)),
-		new LDComment (fmt ("Author: %1", author)),
-		new LDComment (fmt ("!LDRAW_ORG Unofficial_%1Primitive", divs == 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)
-{	str 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 ? hires : lores);
-
-	// If the current value is 16 and we switch to hi-res, default the
-	// spinbox to 48.
-	if (on && ui->sb_segs->value() == lores)
-		ui->sb_segs->setValue (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() ? hires : 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/types.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,613 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QObject>
+#include <QStringList>
+#include <QTextStream>
+#include <QFile>
+#include <assert.h>
+#include "main.h"
+#include "types.h"
+#include "misc.h"
+#include "ldtypes.h"
+#include "document.h"
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str DoFormat (QList<StringFormatArg> args)
+{	assert (args.size() >= 1);
+	str text = args[0].value();
+
+	for (uchar i = 1; i < args.size(); ++i)
+		text = text.arg (args[i].value());
+
+	return text;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+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];
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+vertex vertex::midpoint (const vertex& other)
+{	vertex mid;
+
+	for_axes (ax)
+		mid[ax] = (m_coords[ax] + other[ax]) / 2;
+
+	return mid;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str vertex::stringRep (bool mangled) const
+{	str fmtstr = "%1 %2 %3";
+
+	if (mangled)
+		fmtstr = "(%1, %2, %3)";
+
+	return fmt (fmtstr, coord (X), coord (Y), coord (Z));
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void vertex::transform (matrix matr, 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);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+double& vertex::operator[] (const Axis ax)
+{	return coord ( (int) ax);
+}
+
+const double& vertex::operator[] (const Axis ax) const
+{	return coord ( (int) ax);
+}
+
+double& vertex::operator[] (const int ax)
+{	return coord (ax);
+}
+
+const double& vertex::operator[] (const int ax) const
+{	return coord (ax);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool vertex::operator== (const vertex& other) const
+{	return coord (X) == other[X] &&
+		   coord (Y) == other[Y] &&
+		   coord (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 (coord (X) < other[X])
+		return true;
+
+	if (coord (X) > other[X])
+		return false;
+
+	if (coord (Y) < other[Y])
+		return true;
+
+	if (coord (Y) > other[Y])
+		return false;
+
+	return coord (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 (initlist<double> vals)
+{	assert (vals.size() == 9);
+	memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void matrix::puts() const
+{	for (int i = 0; i < 3; ++i)
+	{	for (int j = 0; j < 3; ++j)
+			log ("%1\t", m_vals[ (i * 3) + j]);
+
+		log ("\n");
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str matrix::stringRep() const
+{	str val;
+
+	for (int i = 0; i < 9; ++i)
+	{	if (i > 0)
+			val += ' ';
+
+		val += str::number (m_vals[i]);
+	}
+
+	return val;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void matrix::zero()
+{	memset (&m_vals[0], 0, sizeof m_vals);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+matrix matrix::mult (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= (matrix other)
+{	memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals);
+	return *this;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+double matrix::getDeterminant() const
+{	return (val (0) * val (4) * val (8)) +
+		   (val (1) * val (5) * val (6)) +
+		   (val (2) * val (3) * val (7)) -
+		   (val (2) * val (4) * val (6)) -
+		   (val (1) * val (3) * val (8)) -
+		   (val (0) * val (5) * val (7));
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool matrix::operator== (const matrix& other) const
+{	for (int i = 0; i < 9; ++i)
+		if (val (i) != other[i])
+			return false;
+
+	return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+StringFormatArg::StringFormatArg (const str& v)
+{	m_val = v;
+}
+
+StringFormatArg::StringFormatArg (const char& v)
+{	m_val = v;
+}
+
+StringFormatArg::StringFormatArg (const uchar& v)
+{	m_val = v;
+}
+
+StringFormatArg::StringFormatArg (const QChar& v)
+{	m_val = v;
+}
+
+StringFormatArg::StringFormatArg (const float& v)
+{	m_val = str::number (v);
+}
+
+StringFormatArg::StringFormatArg (const double& v)
+{	m_val = str::number (v);
+}
+
+StringFormatArg::StringFormatArg (const vertex& v)
+{	m_val = v.stringRep (false);
+}
+
+StringFormatArg::StringFormatArg (const matrix& v)
+{	m_val = v.stringRep();
+}
+
+StringFormatArg::StringFormatArg (const char* v)
+{	m_val = v;
+}
+
+StringFormatArg::StringFormatArg (const StringConfig& v)
+{	m_val = v.value;
+}
+
+StringFormatArg::StringFormatArg (const IntConfig& v)
+{	m_val.number (v.value);
+}
+
+StringFormatArg::StringFormatArg (const FloatConfig& v)
+{	m_val.number (v.value);
+}
+
+StringFormatArg::StringFormatArg (const void* v)
+{	m_val.sprintf ("%p", v);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+File::File()
+{	// Make a null file
+	m_file = null;
+	m_textstream = null;
+}
+
+File::File (str path, OpenType rtype)
+{	m_file = null;
+	open (path, rtype);
+}
+
+File::File (FILE* fp, OpenType rtype)
+{	m_file = null;
+	open (fp, rtype);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+File::~File()
+{	if (m_file)
+	{	m_file->close();
+		delete m_file;
+
+		if (m_textstream)
+			delete m_textstream;
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool File::open (FILE* fp, OpenType rtype)
+{	return open ("", rtype, fp);
+}
+
+bool File::open (str path, OpenType rtype, FILE* fp)
+{	close();
+
+	if (!m_file)
+		m_file = new QFile;
+
+	m_file->setFileName (path);
+
+	bool result;
+
+	QIODevice::OpenMode mode =
+		(rtype == Read) ? QIODevice::ReadOnly :
+		(rtype == Write) ? QIODevice::WriteOnly : QIODevice::Append;
+
+	if (fp)
+		result = m_file->open (fp, mode);
+	else
+		result = m_file->open (mode);
+
+	if (result)
+	{	m_textstream = new QTextStream (m_file);
+		return true;
+	}
+
+	delete m_file;
+	m_file = null;
+	return false;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+File::iterator File::begin()
+{	return iterator (this);
+}
+
+File::iterator& File::end()
+{	return m_endIterator;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void File::write (str msg)
+{	m_file->write (msg.toUtf8(), msg.length());
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool File::readLine (str& line)
+{	if (!m_textstream || m_textstream->atEnd())
+		return false;
+
+	line = m_textstream->readLine();
+	return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool File::atEnd() const
+{	assert (m_textstream != null);
+	return m_textstream->atEnd();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool File::isNull() const
+{	return m_file == null;
+}
+
+bool File::operator!() const
+{	return isNull();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void File::close()
+{	if (!m_file)
+		return;
+
+	delete m_file;
+	m_file = null;
+
+	if (m_textstream)
+	{	delete m_textstream;
+		m_textstream = null;
+	}
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool File::flush()
+{	return m_file->flush();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+File::operator bool() const
+{	return !isNull();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void File::rewind()
+{	m_file->seek (0);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+File::iterator::iterator (File* f) : m_file (f)
+{	operator++();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void File::iterator::operator++()
+{	m_gotdata = m_file->readLine (m_text);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str File::iterator::operator*()
+{	return m_text;
+}
+
+// =============================================================================
+// The prime contestant for the weirdest operator== 2013 award?
+// -----------------------------------------------------------------------------
+bool File::iterator::operator== (File::iterator& other)
+{	return (other.m_file == null && !m_gotdata);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool File::iterator::operator!= (File::iterator& other)
+{	return !operator== (other);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDBoundingBox::LDBoundingBox()
+{	reset();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDBoundingBox::calculate()
+{	reset();
+
+	if (!getCurrentDocument())
+		return;
+
+	for (LDObject* obj : getCurrentDocument()->getObjects())
+		calcObject (obj);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDBoundingBox::calcObject (LDObject* obj)
+{	switch (obj->getType())
+	{	case LDObject::Line:
+		case LDObject::Triangle:
+		case LDObject::Quad:
+		case LDObject::CondLine:
+		{	for (int i = 0; i < obj->vertices(); ++i)
+				calcVertex (obj->getVertex (i));
+		} break;
+
+		case LDObject::Subfile:
+		{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepCacheInline);
+
+			for (LDObject * obj : objs)
+			{	calcObject (obj);
+				delete obj;
+			}
+		}
+		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& v)
+{	for_axes (ax)
+	{	if (v[ax] < m_Vertex0[ax])
+			m_Vertex0[ax] = v[ax];
+
+		if (v[ax] > m_Vertex1[ax])
+			m_Vertex1[ax] = v[ax];
+	}
+
+	setEmpty (false);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void LDBoundingBox::reset()
+{	m_Vertex0[X] = m_Vertex0[Y] = m_Vertex0[Z] = 0x7FFFFFFF;
+	m_Vertex1[X] = m_Vertex1[Y] = m_Vertex1[Z] = 0xFFFFFFFF;
+
+	setEmpty (true);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+double LDBoundingBox::size() 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.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,613 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QObject>
-#include <QStringList>
-#include <QTextStream>
-#include <QFile>
-#include <assert.h>
-#include "main.h"
-#include "types.h"
-#include "misc.h"
-#include "ldtypes.h"
-#include "document.h"
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str DoFormat (QList<StringFormatArg> args)
-{	assert (args.size() >= 1);
-	str text = args[0].value();
-
-	for (uchar i = 1; i < args.size(); ++i)
-		text = text.arg (args[i].value());
-
-	return text;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-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 (const Axis ax : g_Axes)
-		m_coords[ax] += other[ax];
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-vertex vertex::midpoint (const vertex& other)
-{	vertex mid;
-
-	for (const Axis ax : g_Axes)
-		mid[ax] = (m_coords[ax] + other[ax]) / 2;
-
-	return mid;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str vertex::stringRep (bool mangled) const
-{	str fmtstr = "%1 %2 %3";
-
-	if (mangled)
-		fmtstr = "(%1, %2, %3)";
-
-	return fmt (fmtstr, coord (X), coord (Y), coord (Z));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void vertex::transform (matrix matr, 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);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-double& vertex::operator[] (const Axis ax)
-{	return coord ( (int) ax);
-}
-
-const double& vertex::operator[] (const Axis ax) const
-{	return coord ( (int) ax);
-}
-
-double& vertex::operator[] (const int ax)
-{	return coord (ax);
-}
-
-const double& vertex::operator[] (const int ax) const
-{	return coord (ax);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool vertex::operator== (const vertex& other) const
-{	return coord (X) == other[X] &&
-		   coord (Y) == other[Y] &&
-		   coord (Z) == other[Z];
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-vertex& vertex::operator/= (const double d)
-{	for (const Axis ax : g_Axes)
-		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 (coord (X) < other[X])
-		return true;
-
-	if (coord (X) > other[X])
-		return false;
-
-	if (coord (Y) < other[Y])
-		return true;
-
-	if (coord (Y) > other[Y])
-		return false;
-
-	return coord (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 (initlist<double> vals)
-{	assert (vals.size() == 9);
-	memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void matrix::puts() const
-{	for (int i = 0; i < 3; ++i)
-	{	for (int j = 0; j < 3; ++j)
-			log ("%1\t", m_vals[ (i * 3) + j]);
-
-		log ("\n");
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str matrix::stringRep() const
-{	str val;
-
-	for (int i = 0; i < 9; ++i)
-	{	if (i > 0)
-			val += ' ';
-
-		val += str::number (m_vals[i]);
-	}
-
-	return val;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void matrix::zero()
-{	memset (&m_vals[0], 0, sizeof m_vals);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-matrix matrix::mult (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= (matrix other)
-{	memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals);
-	return *this;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-double matrix::getDeterminant() const
-{	return (val (0) * val (4) * val (8)) +
-		   (val (1) * val (5) * val (6)) +
-		   (val (2) * val (3) * val (7)) -
-		   (val (2) * val (4) * val (6)) -
-		   (val (1) * val (3) * val (8)) -
-		   (val (0) * val (5) * val (7));
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool matrix::operator== (const matrix& other) const
-{	for (int i = 0; i < 9; ++i)
-		if (val (i) != other[i])
-			return false;
-
-	return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-StringFormatArg::StringFormatArg (const str& v)
-{	m_val = v;
-}
-
-StringFormatArg::StringFormatArg (const char& v)
-{	m_val = v;
-}
-
-StringFormatArg::StringFormatArg (const uchar& v)
-{	m_val = v;
-}
-
-StringFormatArg::StringFormatArg (const QChar& v)
-{	m_val = v;
-}
-
-StringFormatArg::StringFormatArg (const float& v)
-{	m_val = str::number (v);
-}
-
-StringFormatArg::StringFormatArg (const double& v)
-{	m_val = str::number (v);
-}
-
-StringFormatArg::StringFormatArg (const vertex& v)
-{	m_val = v.stringRep (false);
-}
-
-StringFormatArg::StringFormatArg (const matrix& v)
-{	m_val = v.stringRep();
-}
-
-StringFormatArg::StringFormatArg (const char* v)
-{	m_val = v;
-}
-
-StringFormatArg::StringFormatArg (const StringConfig& v)
-{	m_val = v.value;
-}
-
-StringFormatArg::StringFormatArg (const IntConfig& v)
-{	m_val.number (v.value);
-}
-
-StringFormatArg::StringFormatArg (const FloatConfig& v)
-{	m_val.number (v.value);
-}
-
-StringFormatArg::StringFormatArg (const void* v)
-{	m_val.sprintf ("%p", v);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-File::File()
-{	// Make a null file
-	m_file = null;
-	m_textstream = null;
-}
-
-File::File (str path, OpenType rtype)
-{	m_file = null;
-	open (path, rtype);
-}
-
-File::File (FILE* fp, OpenType rtype)
-{	m_file = null;
-	open (fp, rtype);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-File::~File()
-{	if (m_file)
-	{	m_file->close();
-		delete m_file;
-
-		if (m_textstream)
-			delete m_textstream;
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool File::open (FILE* fp, OpenType rtype)
-{	return open ("", rtype, fp);
-}
-
-bool File::open (str path, OpenType rtype, FILE* fp)
-{	close();
-
-	if (!m_file)
-		m_file = new QFile;
-
-	m_file->setFileName (path);
-
-	bool result;
-
-	QIODevice::OpenMode mode =
-		(rtype == Read) ? QIODevice::ReadOnly :
-		(rtype == Write) ? QIODevice::WriteOnly : QIODevice::Append;
-
-	if (fp)
-		result = m_file->open (fp, mode);
-	else
-		result = m_file->open (mode);
-
-	if (result)
-	{	m_textstream = new QTextStream (m_file);
-		return true;
-	}
-
-	delete m_file;
-	m_file = null;
-	return false;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-File::iterator File::begin()
-{	return iterator (this);
-}
-
-File::iterator& File::end()
-{	return m_endIterator;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void File::write (str msg)
-{	m_file->write (msg.toUtf8(), msg.length());
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool File::readLine (str& line)
-{	if (!m_textstream || m_textstream->atEnd())
-		return false;
-
-	line = m_textstream->readLine();
-	return true;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool File::atEnd() const
-{	assert (m_textstream != null);
-	return m_textstream->atEnd();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool File::isNull() const
-{	return m_file == null;
-}
-
-bool File::operator!() const
-{	return isNull();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void File::close()
-{	if (!m_file)
-		return;
-
-	delete m_file;
-	m_file = null;
-
-	if (m_textstream)
-	{	delete m_textstream;
-		m_textstream = null;
-	}
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool File::flush()
-{	return m_file->flush();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-File::operator bool() const
-{	return !isNull();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void File::rewind()
-{	m_file->seek (0);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-File::iterator::iterator (File* f) : m_file (f)
-{	operator++();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void File::iterator::operator++()
-{	m_gotdata = m_file->readLine (m_text);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str File::iterator::operator*()
-{	return m_text;
-}
-
-// =============================================================================
-// The prime contestant for the weirdest operator== 2013 award?
-// -----------------------------------------------------------------------------
-bool File::iterator::operator== (File::iterator& other)
-{	return (other.m_file == null && !m_gotdata);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-bool File::iterator::operator!= (File::iterator& other)
-{	return !operator== (other);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDBoundingBox::LDBoundingBox()
-{	reset();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDBoundingBox::calculate()
-{	reset();
-
-	if (!getCurrentDocument())
-		return;
-
-	for (LDObject* obj : getCurrentDocument()->getObjects())
-		calcObject (obj);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDBoundingBox::calcObject (LDObject* obj)
-{	switch (obj->getType())
-	{	case LDObject::Line:
-		case LDObject::Triangle:
-		case LDObject::Quad:
-		case LDObject::CondLine:
-		{	for (int i = 0; i < obj->vertices(); ++i)
-				calcVertex (obj->getVertex (i));
-		} break;
-
-		case LDObject::Subfile:
-		{	LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepCacheInline);
-
-			for (LDObject * obj : objs)
-			{	calcObject (obj);
-				delete obj;
-			}
-		}
-		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& v)
-{	for (const Axis ax : g_Axes)
-	{	if (v[ax] < m_Vertex0[ax])
-			m_Vertex0[ax] = v[ax];
-
-		if (v[ax] > m_Vertex1[ax])
-			m_Vertex1[ax] = v[ax];
-	}
-
-	setEmpty (false);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-void LDBoundingBox::reset()
-{	m_Vertex0[X] = m_Vertex0[Y] = m_Vertex0[Z] = 0x7FFFFFFF;
-	m_Vertex1[X] = m_Vertex1[Y] = m_Vertex1[Z] = 0xFFFFFFFF;
-
-	setEmpty (true);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-double LDBoundingBox::size() 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	Fri Dec 13 00:39:49 2013 +0200
+++ b/src/types.h	Fri Dec 13 20:01:49 2013 +0200
@@ -49,8 +49,11 @@
 template<class T, class R>
 using pair = std::pair<T, R>;
 
-enum Axis { X, Y, Z };
-static const Axis g_Axes[3] = { X, Y, Z };
+enum Axis
+{	X,
+	Y,
+	Z
+};
 
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets.cc	Fri Dec 13 20:01:49 2013 +0200
@@ -0,0 +1,177 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// 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"
+#include "moc_widgets.cpp"
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+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, initlist<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.cpp	Fri Dec 13 00:39:49 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,177 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-// 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"
-#include "moc_widgets.cpp"
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-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, initlist<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();
-}

mercurial