- renamed .cc files to .cpp

Tue, 03 Mar 2015 16:55:36 +0200

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Tue, 03 Mar 2015 16:55:36 +0200
changeset 931
85080f7a1c20
parent 929
66e8453e1768
child 932
738673e8a6b4

- renamed .cc files to .cpp

CMakeLists.txt file | annotate | diff | comparison | revisions
src/actions.cc file | annotate | diff | comparison | revisions
src/actions.cpp file | annotate | diff | comparison | revisions
src/actionsEdit.cc file | annotate | diff | comparison | revisions
src/actionsEdit.cpp file | annotate | diff | comparison | revisions
src/addObjectDialog.cc file | annotate | diff | comparison | revisions
src/addObjectDialog.cpp file | annotate | diff | comparison | revisions
src/basics.cc file | annotate | diff | comparison | revisions
src/basics.cpp file | annotate | diff | comparison | revisions
src/colorSelector.cc file | annotate | diff | comparison | revisions
src/colorSelector.cpp file | annotate | diff | comparison | revisions
src/colors.cc file | annotate | diff | comparison | revisions
src/colors.cpp file | annotate | diff | comparison | revisions
src/configDialog.cc file | annotate | diff | comparison | revisions
src/configDialog.cpp file | annotate | diff | comparison | revisions
src/configuration.cc file | annotate | diff | comparison | revisions
src/configuration.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/documentation.cc file | annotate | diff | comparison | revisions
src/documentation.cpp file | annotate | diff | comparison | revisions
src/editHistory.cc file | annotate | diff | comparison | revisions
src/editHistory.cpp file | annotate | diff | comparison | revisions
src/extPrograms.cc file | annotate | diff | comparison | revisions
src/extPrograms.cpp file | annotate | diff | comparison | revisions
src/glCompiler.cc file | annotate | diff | comparison | revisions
src/glCompiler.cpp file | annotate | diff | comparison | revisions
src/glRenderer.cc file | annotate | diff | comparison | revisions
src/glRenderer.cpp file | annotate | diff | comparison | revisions
src/ldConfig.cc file | annotate | diff | comparison | revisions
src/ldConfig.cpp file | annotate | diff | comparison | revisions
src/ldDocument.cc file | annotate | diff | comparison | revisions
src/ldDocument.cpp file | annotate | diff | comparison | revisions
src/ldObject.cc file | annotate | diff | comparison | revisions
src/ldObject.cpp file | annotate | diff | comparison | revisions
src/main.cc file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/mainWindow.cc file | annotate | diff | comparison | revisions
src/mainWindow.cpp file | annotate | diff | comparison | revisions
src/messageLog.cc file | annotate | diff | comparison | revisions
src/messageLog.cpp file | annotate | diff | comparison | revisions
src/miscallenous.cc file | annotate | diff | comparison | revisions
src/miscallenous.cpp file | annotate | diff | comparison | revisions
src/partDownloader.cc file | annotate | diff | comparison | revisions
src/partDownloader.cpp file | annotate | diff | comparison | revisions
src/primitives.cc file | annotate | diff | comparison | revisions
src/primitives.cpp file | annotate | diff | comparison | revisions
src/radioGroup.cc file | annotate | diff | comparison | revisions
src/radioGroup.cpp file | annotate | diff | comparison | revisions
src/ringFinder.cc file | annotate | diff | comparison | revisions
src/ringFinder.cpp file | annotate | diff | comparison | revisions
src/version.cc file | annotate | diff | comparison | revisions
src/version.cpp file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Sat Feb 21 20:23:34 2015 +0200
+++ b/CMakeLists.txt	Tue Mar 03 16:55:36 2015 +0200
@@ -28,41 +28,41 @@
 include_directories (${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
 
 set (LDFORGE_SOURCES
-	src/actions.cc
-	src/actionsEdit.cc
-	src/addObjectDialog.cc
-	src/basics.cc
-	src/colors.cc
-	src/colorSelector.cc
-	src/configuration.cc
-	src/configDialog.cc
-	src/crashCatcher.cc
-	src/dialogs.cc
-	src/documentation.cc
-	src/editHistory.cc
-	src/extPrograms.cc
-	src/glRenderer.cc
-	src/glCompiler.cc
-	src/ldConfig.cc
-	src/ldDocument.cc
-	src/ldObject.cc
-    src/ldObjectMath.cpp
-	src/main.cc
-	src/mainWindow.cc
-	src/messageLog.cc
-	src/miscallenous.cc
-	src/partDownloader.cc
-	src/primitives.cc
-	src/radioGroup.cc
-	src/ringFinder.cc
-	src/version.cc
-	src/editmodes/abstractEditMode.cc
-	src/editmodes/circleMode.cc
-	src/editmodes/drawMode.cc
-    src/editmodes/linePathMode.cpp
-	src/editmodes/magicWandMode.cc
-	src/editmodes/rectangleMode.cc
-	src/editmodes/selectMode.cc
+	src/actions.cpp
+	src/actionsEdit.cpp
+	src/addObjectDialog.cpp
+	src/basics.cpp
+	src/colors.cpp
+	src/colorSelector.cpp
+	src/configuration.cpp
+	src/configDialog.cpp
+	src/crashCatcher.cpp
+	src/dialogs.cpp
+	src/documentation.cpp
+	src/editHistory.cpp
+	src/extPrograms.cpp
+	src/glRenderer.cpp
+	src/glCompiler.cpp
+	src/ldConfig.cpp
+	src/ldDocument.cpp
+	src/ldObject.cpp
+	src/ldObjectMath.cpp
+	src/main.cpp
+	src/mainWindow.cpp
+	src/messageLog.cpp
+	src/miscallenous.cpp
+	src/partDownloader.cpp
+	src/primitives.cpp
+	src/radioGroup.cpp
+	src/ringFinder.cpp
+	src/version.cpp
+	src/editmodes/abstractEditMode.cpp
+	src/editmodes/circleMode.cpp
+	src/editmodes/drawMode.cpp
+	src/editmodes/linePathMode.cpp
+	src/editmodes/magicWandMode.cpp
+	src/editmodes/rectangleMode.cpp
+	src/editmodes/selectMode.cpp
 )
 
 set (LDFORGE_HEADERS
@@ -194,4 +194,4 @@
 	COMMAND python
 		"${CMAKE_SOURCE_DIR}/updaterevision.py"
 		"${CMAKE_CURRENT_BINARY_DIR}/hginfo.h")
-add_dependencies (${PROJECT_NAME} make_hginfo_h)
\ No newline at end of file
+add_dependencies (${PROJECT_NAME} make_hginfo_h)
--- a/src/actions.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,911 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QFileDialog>
-#include <QMessageBox>
-#include <QTextEdit>
-#include <QBoxLayout>
-#include <QDialogButtonBox>
-#include <QPushButton>
-#include <QInputDialog>
-
-#include "mainWindow.h"
-#include "ldDocument.h"
-#include "editHistory.h"
-#include "configDialog.h"
-#include "addObjectDialog.h"
-#include "miscallenous.h"
-#include "glRenderer.h"
-#include "dialogs.h"
-#include "primitives.h"
-#include "radioGroup.h"
-#include "colors.h"
-#include "glCompiler.h"
-#include "ui_newpart.h"
-#include "editmodes/abstractEditMode.h"
-
-EXTERN_CFGENTRY (Bool, DrawWireframe)
-EXTERN_CFGENTRY (Bool, BFCRedGreenView)
-EXTERN_CFGENTRY (String, DefaultName)
-EXTERN_CFGENTRY (String, DefaultUser)
-EXTERN_CFGENTRY (Bool, UseCALicense)
-EXTERN_CFGENTRY (Bool, DrawAngles)
-EXTERN_CFGENTRY (Bool, RandomColors)
-EXTERN_CFGENTRY (Bool, DrawSurfaces)
-EXTERN_CFGENTRY (Bool, DrawEdgeLines)
-EXTERN_CFGENTRY (Bool, DrawConditionalLines)
-EXTERN_CFGENTRY (Bool, DrawAxes)
-
-// =============================================================================
-//
-void MainWindow::actionNew()
-{
-	QDialog* dlg = new QDialog (g_win);
-	Ui::NewPartUI ui;
-	ui.setupUi (dlg);
-
-	QString authortext = cfg::DefaultName;
-
-	if (not cfg::DefaultUser.isEmpty())
-		authortext.append (format (" [%1]", cfg::DefaultUser));
-
-	ui.le_author->setText (authortext);
-	ui.caLicense->setChecked (cfg::UseCALicense);
-
-	if (dlg->exec() == QDialog::Rejected)
-		return;
-
-	newFile();
-
-	BFCStatement const bfctype = ui.rb_bfc_ccw->isChecked() ? BFCStatement::CertifyCCW
-							   : ui.rb_bfc_cw->isChecked()  ? BFCStatement::CertifyCW
-							   : BFCStatement::NoCertify;
-	QString const license = ui.caLicense->isChecked() ? CALicenseText : "";
-
-	LDObjectList objs;
-	objs << LDSpawn<LDComment> (ui.le_title->text());
-	objs << LDSpawn<LDComment> ("Name: <untitled>.dat");
-	objs << LDSpawn<LDComment> (format ("Author: %1", ui.le_author->text()));
-	objs << LDSpawn<LDComment> (format ("!LDRAW_ORG Unofficial_Part"));
-
-	if (not license.isEmpty())
-		objs << LDSpawn<LDComment> (license);
-
-	objs << LDSpawn<LDEmpty>();
-	objs << LDSpawn<LDBFC> (bfctype);
-	objs << LDSpawn<LDEmpty>();
-	CurrentDocument()->addObjects (objs);
-	doFullRefresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionNewFile()
-{
-	newFile();
-}
-
-// =============================================================================
-//
-void MainWindow::actionOpen()
-{
-	QString name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)");
-
-	if (name.isEmpty())
-		return;
-
-	OpenMainModel (name);
-}
-
-// =============================================================================
-//
-void MainWindow::actionSave()
-{
-	save (CurrentDocument(), false);
-}
-
-// =============================================================================
-//
-void MainWindow::actionSaveAs()
-{
-	save (CurrentDocument(), true);
-}
-
-// =============================================================================
-//
-void MainWindow::actionSaveAll()
-{
-	for (LDDocumentPtr file : LDDocument::explicitDocuments())
-		save (file, false);
-}
-
-// =============================================================================
-//
-void MainWindow::actionClose()
-{
-	if (not CurrentDocument()->isSafeToClose())
-		return;
-
-	CurrentDocument()->dismiss();
-}
-
-// =============================================================================
-//
-void MainWindow::actionCloseAll()
-{
-	if (not IsSafeToCloseAll())
-		return;
-
-	CloseAllDocuments();
-}
-
-// =============================================================================
-//
-void MainWindow::actionSettings()
-{
-	(new ConfigDialog)->exec();
-}
-
-// =============================================================================
-//
-void MainWindow::actionSetLDrawPath()
-{
-	(new LDrawPathDialog (true))->exec();
-}
-
-// =============================================================================
-//
-void MainWindow::actionExit()
-{
-	Exit();
-}
-
-// =============================================================================
-//
-void MainWindow::actionNewSubfile()
-{
-	AddObjectDialog::staticDialog (OBJ_Subfile, LDObjectPtr());
-}
-
-// =============================================================================
-//
-void MainWindow::actionNewLine()
-{
-	AddObjectDialog::staticDialog (OBJ_Line, LDObjectPtr());
-}
-
-// =============================================================================
-//
-void MainWindow::actionNewTriangle()
-{
-	AddObjectDialog::staticDialog (OBJ_Triangle, LDObjectPtr());
-}
-
-// =============================================================================
-//
-void MainWindow::actionNewQuad()
-{
-	AddObjectDialog::staticDialog (OBJ_Quad, LDObjectPtr());
-}
-
-// =============================================================================
-//
-void MainWindow::actionNewCLine()
-{
-	AddObjectDialog::staticDialog (OBJ_CondLine, LDObjectPtr());
-}
-
-// =============================================================================
-//
-void MainWindow::actionNewComment()
-{
-	AddObjectDialog::staticDialog (OBJ_Comment, LDObjectPtr());
-}
-
-// =============================================================================
-//
-void MainWindow::actionNewBFC()
-{
-	AddObjectDialog::staticDialog (OBJ_BFC, LDObjectPtr());
-}
-
-// =============================================================================
-//
-void MainWindow::actionEdit()
-{
-	if (Selection().size() != 1)
-		return;
-
-	LDObjectPtr obj = Selection() [0];
-	AddObjectDialog::staticDialog (obj->type(), obj);
-}
-
-// =============================================================================
-//
-void MainWindow::actionHelp()
-{
-}
-
-// =============================================================================
-//
-void MainWindow::actionAbout()
-{
-	AboutDialog().exec();
-}
-
-// =============================================================================
-//
-void MainWindow::actionAboutQt()
-{
-	QMessageBox::aboutQt (g_win);
-}
-
-// =============================================================================
-//
-void MainWindow::actionSelectAll()
-{
-	for (LDObjectPtr obj : CurrentDocument()->objects())
-		obj->select();
-}
-
-// =============================================================================
-//
-void MainWindow::actionSelectByColor()
-{
-	if (Selection().isEmpty())
-		return;
-
-	QList<LDColor> colors;
-
-	for (LDObjectPtr obj : Selection())
-	{
-		if (obj->isColored())
-			colors << obj->color();
-	}
-
-	RemoveDuplicates (colors);
-	CurrentDocument()->clearSelection();
-
-	for (LDObjectPtr obj : CurrentDocument()->objects())
-	{
-		if (colors.contains (obj->color()))
-			obj->select();
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::actionSelectByType()
-{
-	if (Selection().isEmpty())
-		return;
-
-	QList<LDObjectType> types;
-	QStringList subfilenames;
-
-	for (LDObjectPtr obj : Selection())
-	{
-		types << obj->type();
-
-		if (types.last() == OBJ_Subfile)
-			subfilenames << obj.staticCast<LDSubfile>()->fileInfo()->name();
-	}
-
-	RemoveDuplicates (types);
-	RemoveDuplicates (subfilenames);
-	CurrentDocument()->clearSelection();
-
-	for (LDObjectPtr obj : CurrentDocument()->objects())
-	{
-		LDObjectType type = obj->type();
-
-		if (not types.contains (type))
-			continue;
-
-		// For subfiles, type check is not enough, we check the name of the document as well.
-		if (type == OBJ_Subfile and not subfilenames.contains (obj.staticCast<LDSubfile>()->fileInfo()->name()))
-			continue;
-
-		obj->select();
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::actionGridCoarse()
-{
-	cfg::Grid = Grid::Coarse;
-	updateGridToolBar();
-}
-
-void MainWindow::actionGridMedium()
-{
-	cfg::Grid = Grid::Medium;
-	updateGridToolBar();
-}
-
-void MainWindow::actionGridFine()
-{
-	cfg::Grid = Grid::Fine;
-	updateGridToolBar();
-}
-
-// =============================================================================
-//
-void MainWindow::actionResetView()
-{
-	R()->resetAngles();
-	R()->update();
-}
-
-// =============================================================================
-//
-void MainWindow::actionInsertFrom()
-{
-	QString fname = QFileDialog::getOpenFileName();
-	int idx = getInsertionPoint();
-
-	if (not fname.length())
-		return;
-
-	QFile f (fname);
-
-	if (not f.open (QIODevice::ReadOnly))
-	{
-		Critical (format ("Couldn't open %1 (%2)", fname, f.errorString()));
-		return;
-	}
-
-	LDObjectList objs = LoadFileContents (&f, null);
-
-	CurrentDocument()->clearSelection();
-
-	for (LDObjectPtr obj : objs)
-	{
-		CurrentDocument()->insertObj (idx, obj);
-		obj->select();
-		R()->compileObject (obj);
-
-		idx++;
-	}
-
-	refresh();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-void MainWindow::actionExportTo()
-{
-	if (Selection().isEmpty())
-		return;
-
-	QString fname = QFileDialog::getSaveFileName();
-
-	if (fname.length() == 0)
-		return;
-
-	QFile file (fname);
-
-	if (not file.open (QIODevice::WriteOnly | QIODevice::Text))
-	{
-		Critical (format ("Unable to open %1 for writing (%2)", fname, file.errorString()));
-		return;
-	}
-
-	for (LDObjectPtr obj : Selection())
-	{
-		QString contents = obj->asText();
-		QByteArray data = contents.toUtf8();
-		file.write (data, data.size());
-		file.write ("\r\n", 2);
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::actionInsertRaw()
-{
-	int idx = getInsertionPoint();
-
-	QDialog* const dlg = new QDialog;
-	QVBoxLayout* const layout = new QVBoxLayout;
-	QTextEdit* const te_edit = new QTextEdit;
-	QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-
-	layout->addWidget (te_edit);
-	layout->addWidget (bbx_buttons);
-	dlg->setLayout (layout);
-	dlg->setWindowTitle (APPNAME " - Insert Raw");
-	dlg->connect (bbx_buttons, SIGNAL (accepted()), dlg, SLOT (accept()));
-	dlg->connect (bbx_buttons, SIGNAL (rejected()), dlg, SLOT (reject()));
-
-	if (dlg->exec() == QDialog::Rejected)
-		return;
-
-	CurrentDocument()->clearSelection();
-
-	for (QString line : QString (te_edit->toPlainText()).split ("\n"))
-	{
-		LDObjectPtr obj = ParseLine (line);
-
-		CurrentDocument()->insertObj (idx, obj);
-		obj->select();
-		idx++;
-	}
-
-	refresh();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-void MainWindow::actionScreenshot()
-{
-	setlocale (LC_ALL, "C");
-
-	int w, h;
-	uchar* imgdata = R()->getScreencap (w, h);
-	QImage img = GetImageFromScreencap (imgdata, w, h);
-
-	QString root = Basename (CurrentDocument()->name());
-
-	if (root.right (4) == ".dat")
-		root.chop (4);
-
-	QString defaultname = (root.length() > 0) ? format ("%1.png", root) : "";
-	QString fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname,
-				"PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)");
-
-	if (not fname.isEmpty() and not img.save (fname))
-		Critical (format ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno)));
-
-	delete[] imgdata;
-}
-
-// =============================================================================
-//
-void MainWindow::actionAxes()
-{
-	cfg::DrawAxes = not cfg::DrawAxes;
-	updateActions();
-	R()->update();
-}
-
-// =============================================================================
-//
-void MainWindow::actionVisibilityToggle()
-{
-	for (LDObjectPtr obj : Selection())
-		obj->setHidden (not obj->isHidden());
-
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionVisibilityHide()
-{
-	for (LDObjectPtr obj : Selection())
-		obj->setHidden (true);
-
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionVisibilityReveal()
-{
-	for (LDObjectPtr obj : Selection())
-	obj->setHidden (false);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionWireframe()
-{
-	cfg::DrawWireframe = not cfg::DrawWireframe;
-	R()->refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionSetOverlay()
-{
-	OverlayDialog dlg;
-
-	if (not dlg.exec())
-		return;
-
-	R()->setupOverlay ((ECamera) dlg.camera(), dlg.fpath(), dlg.ofsx(),
-		dlg.ofsy(), dlg.lwidth(), dlg.lheight());
-}
-
-// =============================================================================
-//
-void MainWindow::actionClearOverlay()
-{
-	R()->clearOverlay();
-}
-
-// =============================================================================
-//
-void MainWindow::actionModeSelect()
-{
-	R()->setEditMode (EditModeType::Select);
-}
-
-// =============================================================================
-//
-void MainWindow::actionModeDraw()
-{
-	R()->setEditMode (EditModeType::Draw);
-}
-
-// =============================================================================
-//
-void MainWindow::actionModeRectangle()
-{
-	R()->setEditMode (EditModeType::Rectangle);
-}
-
-// =============================================================================
-//
-void MainWindow::actionModeCircle()
-{
-	R()->setEditMode (EditModeType::Circle);
-}
-
-// =============================================================================
-//
-void MainWindow::actionModeMagicWand()
-{
- 	R()->setEditMode (EditModeType::MagicWand);
-}
-
-void MainWindow::actionModeLinePath()
-{
-	R()->setEditMode (EditModeType::LinePath);
-}
-
-// =============================================================================
-//
-void MainWindow::actionDrawAngles()
-{
-	cfg::DrawAngles = not cfg::DrawAngles;
-	R()->refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionSetDrawDepth()
-{
-	if (R()->camera() == EFreeCamera)
-		return;
-
-	bool ok;
-	double depth = QInputDialog::getDouble (g_win, "Set Draw Depth",
-											format ("Depth value for %1 Camera:", R()->getCameraName()),
-											R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok);
-
-	if (ok)
-		R()->setDepthValue (depth);
-}
-
-#if 0
-// This is a test to draw a dummy axle. Meant to be used as a primitive gallery,
-// but I can't figure how to generate these pictures properly. Multi-threading
-// these is an immense pain.
-void MainWindow::actiontestpic()
-{
-	LDDocumentPtr file = getFile ("axle.dat");
-	setlocale (LC_ALL, "C");
-
-	if (not 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
-
-// =============================================================================
-//
-void MainWindow::actionScanPrimitives()
-{
-	PrimitiveScanner::start();
-}
-
-// =============================================================================
-//
-void MainWindow::actionBFCView()
-{
-	cfg::BFCRedGreenView = not cfg::BFCRedGreenView;
-
-	if (cfg::BFCRedGreenView)
-		cfg::RandomColors = false;
-
-	updateActions();
-	R()->refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionJumpTo()
-{
-	bool ok;
-	int defval = 0;
-	LDObjectPtr obj;
-
-	if (Selection().size() == 1)
-		defval = Selection()[0]->lineNumber();
-
-	int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval,
-		1, CurrentDocument()->getObjectCount(), 1, &ok);
-
-	if (not ok or (obj = CurrentDocument()->getObject (idx - 1)) == null)
-		return;
-
-	CurrentDocument()->clearSelection();
-	obj->select();
-	updateSelection();
-}
-
-// =============================================================================
-//
-void MainWindow::actionSubfileSelection()
-{
-	if (Selection().size() == 0)
-		return;
-
-	QString			parentpath (CurrentDocument()->fullPath());
-
-	// BFC type of the new subfile - it shall inherit the BFC type of the parent document
-	BFCStatement	bfctype (BFCStatement::NoCertify);
-
-	// Dirname of the new subfile
-	QString			subdirname (Dirname (parentpath));
-
-	// Title of the new subfile
-	QString			subtitle;
-
-	// Comment containing the title of the parent document
-	LDCommentPtr	titleobj (CurrentDocument()->getObject (0).dynamicCast<LDComment>());
-
-	// License text for the subfile
-	QString			license (PreferredLicenseText());
-
-	// LDraw code body of the new subfile (i.e. code of the selection)
-	QStringList		code;
-
-	// Full path of the subfile to be
-	QString			fullsubname;
-
-	// Where to insert the subfile reference?
-	int				refidx (Selection()[0]->lineNumber());
-
-	// Determine title of subfile
-	if (titleobj != null)
-		subtitle = "~" + titleobj->text();
-	else
-		subtitle = "~subfile";
-
-	// Remove duplicate tildes
-	while (subtitle.startsWith ("~~"))
-		subtitle.remove (0, 1);
-
-	// If this the parent document isn't already in s/, we need to stuff it into
-	// a subdirectory named s/. Ensure it exists!
-	QString topdirname = Basename (Dirname (CurrentDocument()->fullPath()));
-
-	if (topdirname != "s")
-	{
-		QString desiredPath = subdirname + "/s";
-		QString title = tr ("Create subfile directory?");
-		QString text = format (tr ("The directory <b>%1</b> is suggested for "
-			"subfiles. This directory does not exist, create it?"), desiredPath);
-
-		if (QDir (desiredPath).exists() or Confirm (title, text))
-		{
-			subdirname = desiredPath;
-			QDir().mkpath (subdirname);
-		}
-		else
-			return;
-	}
-
-	// Determine the body of the name of the subfile
-	if (not parentpath.isEmpty())
-	{
-		// Chop existing '.dat' suffix
-		if (parentpath.endsWith (".dat"))
-			parentpath.chop (4);
-
-		// Remove the s?? suffix if it's there, otherwise we'll get filenames
-		// like s01s01.dat when subfiling subfiles.
-		QRegExp subfilesuffix ("s[0-9][0-9]$");
-		if (subfilesuffix.indexIn (parentpath) != -1)
-			parentpath.chop (subfilesuffix.matchedLength());
-
-		int subidx = 1;
-		QString digits;
-		QFile f;
-		QString testfname;
-
-		// Now find the appropriate filename. Increase the number of the subfile
-		// until we find a name which isn't already taken.
-		do
-		{
-			digits.setNum (subidx++);
-
-			// pad it with a zero
-			if (digits.length() == 1)
-				digits.prepend ("0");
-
-			fullsubname = subdirname + "/" + Basename (parentpath) + "s" + digits + ".dat";
-		} while (FindDocument ("s\\" + Basename (fullsubname)) != null or QFile (fullsubname).exists());
-	}
-
-	// Determine the BFC winding type used in the main document - it is to
-	// be carried over to the subfile.
-	LDIterate<LDBFC> (CurrentDocument()->objects(), [&] (LDBFCPtr const& bfc)
-	{
-		if (Eq (bfc->statement(), BFCStatement::CertifyCCW, BFCStatement::CertifyCW, 
-			BFCStatement::NoCertify))
-		{
-			bfctype = bfc->statement();
-			Break();
-		}
-	});
-
-	// Get the body of the document in LDraw code
-	for (LDObjectPtr obj : Selection())
-		code << obj->asText();
-
-	// Create the new subfile document
-	LDDocumentPtr doc = LDDocument::createNew();
-	doc->setImplicit (false);
-	doc->setFullPath (fullsubname);
-	doc->setName (LDDocument::shortenName (fullsubname));
-
-	LDObjectList objs;
-	objs << LDSpawn<LDComment> (subtitle);
-	objs << LDSpawn<LDComment> ("Name: "); // This gets filled in when the subfile is saved
-	objs << LDSpawn<LDComment> (format ("Author: %1 [%2]", cfg::DefaultName, cfg::DefaultUser));
-	objs << LDSpawn<LDComment> ("!LDRAW_ORG Unofficial_Subpart");
-
-	if (not license.isEmpty())
-		objs << LDSpawn<LDComment> (license);
-
-	objs << LDSpawn<LDEmpty>();
-	objs << LDSpawn<LDBFC> (bfctype);
-	objs << LDSpawn<LDEmpty>();
-
-	doc->addObjects (objs);
-
-	// Add the actual subfile code to the new document
-	for (QString line : code)
-	{
-		LDObjectPtr obj = ParseLine (line);
-		doc->addObject (obj);
-	}
-
-	// Try save it
-	if (save (doc, true))
-	{
-		// Save was successful. Delete the original selection now from the
-		// main document.
-		for (LDObjectPtr obj : Selection())
-			obj->destroy();
-
-		// Add a reference to the new subfile to where the selection was
-		LDSubfilePtr ref (LDSpawn<LDSubfile>());
-		ref->setColor (MainColor());
-		ref->setFileInfo (doc);
-		ref->setPosition (Origin);
-		ref->setTransform (IdentityMatrix);
-		CurrentDocument()->insertObj (refidx, ref);
-
-		// Refresh stuff
-		updateDocumentList();
-		doFullRefresh();
-	}
-	else
-	{
-		// Failed to save.
-		doc->dismiss();
-	}
-}
-
-void MainWindow::actionRandomColors()
-{
-	cfg::RandomColors = not cfg::RandomColors;
-
-	if (cfg::RandomColors)
-		cfg::BFCRedGreenView = false;
-
-	updateActions();
-	R()->refresh();
-}
-
-void MainWindow::actionOpenSubfiles()
-{
-	for (LDObjectPtr obj : Selection())
-	{
-		LDSubfilePtr ref = obj.dynamicCast<LDSubfile>();
-
-		if (ref == null or not ref->fileInfo()->isImplicit())
-			continue;
-
-		ref->fileInfo()->setImplicit (false);
-	}
-}
-
-void MainWindow::actionDrawSurfaces()
-{
-	cfg::DrawSurfaces = not cfg::DrawSurfaces;
-	updateActions();
-	update();
-}
-
-void MainWindow::actionDrawEdgeLines()
-{
-	cfg::DrawEdgeLines = not cfg::DrawEdgeLines;
-	updateActions();
-	update();
-}
-
-void MainWindow::actionDrawConditionalLines()
-{
-	cfg::DrawConditionalLines = not cfg::DrawConditionalLines;
-	updateActions();
-	update();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/actions.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,911 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QTextEdit>
+#include <QBoxLayout>
+#include <QDialogButtonBox>
+#include <QPushButton>
+#include <QInputDialog>
+
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "editHistory.h"
+#include "configDialog.h"
+#include "addObjectDialog.h"
+#include "miscallenous.h"
+#include "glRenderer.h"
+#include "dialogs.h"
+#include "primitives.h"
+#include "radioGroup.h"
+#include "colors.h"
+#include "glCompiler.h"
+#include "ui_newpart.h"
+#include "editmodes/abstractEditMode.h"
+
+EXTERN_CFGENTRY (Bool, DrawWireframe)
+EXTERN_CFGENTRY (Bool, BFCRedGreenView)
+EXTERN_CFGENTRY (String, DefaultName)
+EXTERN_CFGENTRY (String, DefaultUser)
+EXTERN_CFGENTRY (Bool, UseCALicense)
+EXTERN_CFGENTRY (Bool, DrawAngles)
+EXTERN_CFGENTRY (Bool, RandomColors)
+EXTERN_CFGENTRY (Bool, DrawSurfaces)
+EXTERN_CFGENTRY (Bool, DrawEdgeLines)
+EXTERN_CFGENTRY (Bool, DrawConditionalLines)
+EXTERN_CFGENTRY (Bool, DrawAxes)
+
+// =============================================================================
+//
+void MainWindow::actionNew()
+{
+	QDialog* dlg = new QDialog (g_win);
+	Ui::NewPartUI ui;
+	ui.setupUi (dlg);
+
+	QString authortext = cfg::DefaultName;
+
+	if (not cfg::DefaultUser.isEmpty())
+		authortext.append (format (" [%1]", cfg::DefaultUser));
+
+	ui.le_author->setText (authortext);
+	ui.caLicense->setChecked (cfg::UseCALicense);
+
+	if (dlg->exec() == QDialog::Rejected)
+		return;
+
+	newFile();
+
+	BFCStatement const bfctype = ui.rb_bfc_ccw->isChecked() ? BFCStatement::CertifyCCW
+							   : ui.rb_bfc_cw->isChecked()  ? BFCStatement::CertifyCW
+							   : BFCStatement::NoCertify;
+	QString const license = ui.caLicense->isChecked() ? CALicenseText : "";
+
+	LDObjectList objs;
+	objs << LDSpawn<LDComment> (ui.le_title->text());
+	objs << LDSpawn<LDComment> ("Name: <untitled>.dat");
+	objs << LDSpawn<LDComment> (format ("Author: %1", ui.le_author->text()));
+	objs << LDSpawn<LDComment> (format ("!LDRAW_ORG Unofficial_Part"));
+
+	if (not license.isEmpty())
+		objs << LDSpawn<LDComment> (license);
+
+	objs << LDSpawn<LDEmpty>();
+	objs << LDSpawn<LDBFC> (bfctype);
+	objs << LDSpawn<LDEmpty>();
+	CurrentDocument()->addObjects (objs);
+	doFullRefresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionNewFile()
+{
+	newFile();
+}
+
+// =============================================================================
+//
+void MainWindow::actionOpen()
+{
+	QString name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)");
+
+	if (name.isEmpty())
+		return;
+
+	OpenMainModel (name);
+}
+
+// =============================================================================
+//
+void MainWindow::actionSave()
+{
+	save (CurrentDocument(), false);
+}
+
+// =============================================================================
+//
+void MainWindow::actionSaveAs()
+{
+	save (CurrentDocument(), true);
+}
+
+// =============================================================================
+//
+void MainWindow::actionSaveAll()
+{
+	for (LDDocumentPtr file : LDDocument::explicitDocuments())
+		save (file, false);
+}
+
+// =============================================================================
+//
+void MainWindow::actionClose()
+{
+	if (not CurrentDocument()->isSafeToClose())
+		return;
+
+	CurrentDocument()->dismiss();
+}
+
+// =============================================================================
+//
+void MainWindow::actionCloseAll()
+{
+	if (not IsSafeToCloseAll())
+		return;
+
+	CloseAllDocuments();
+}
+
+// =============================================================================
+//
+void MainWindow::actionSettings()
+{
+	(new ConfigDialog)->exec();
+}
+
+// =============================================================================
+//
+void MainWindow::actionSetLDrawPath()
+{
+	(new LDrawPathDialog (true))->exec();
+}
+
+// =============================================================================
+//
+void MainWindow::actionExit()
+{
+	Exit();
+}
+
+// =============================================================================
+//
+void MainWindow::actionNewSubfile()
+{
+	AddObjectDialog::staticDialog (OBJ_Subfile, LDObjectPtr());
+}
+
+// =============================================================================
+//
+void MainWindow::actionNewLine()
+{
+	AddObjectDialog::staticDialog (OBJ_Line, LDObjectPtr());
+}
+
+// =============================================================================
+//
+void MainWindow::actionNewTriangle()
+{
+	AddObjectDialog::staticDialog (OBJ_Triangle, LDObjectPtr());
+}
+
+// =============================================================================
+//
+void MainWindow::actionNewQuad()
+{
+	AddObjectDialog::staticDialog (OBJ_Quad, LDObjectPtr());
+}
+
+// =============================================================================
+//
+void MainWindow::actionNewCLine()
+{
+	AddObjectDialog::staticDialog (OBJ_CondLine, LDObjectPtr());
+}
+
+// =============================================================================
+//
+void MainWindow::actionNewComment()
+{
+	AddObjectDialog::staticDialog (OBJ_Comment, LDObjectPtr());
+}
+
+// =============================================================================
+//
+void MainWindow::actionNewBFC()
+{
+	AddObjectDialog::staticDialog (OBJ_BFC, LDObjectPtr());
+}
+
+// =============================================================================
+//
+void MainWindow::actionEdit()
+{
+	if (Selection().size() != 1)
+		return;
+
+	LDObjectPtr obj = Selection() [0];
+	AddObjectDialog::staticDialog (obj->type(), obj);
+}
+
+// =============================================================================
+//
+void MainWindow::actionHelp()
+{
+}
+
+// =============================================================================
+//
+void MainWindow::actionAbout()
+{
+	AboutDialog().exec();
+}
+
+// =============================================================================
+//
+void MainWindow::actionAboutQt()
+{
+	QMessageBox::aboutQt (g_win);
+}
+
+// =============================================================================
+//
+void MainWindow::actionSelectAll()
+{
+	for (LDObjectPtr obj : CurrentDocument()->objects())
+		obj->select();
+}
+
+// =============================================================================
+//
+void MainWindow::actionSelectByColor()
+{
+	if (Selection().isEmpty())
+		return;
+
+	QList<LDColor> colors;
+
+	for (LDObjectPtr obj : Selection())
+	{
+		if (obj->isColored())
+			colors << obj->color();
+	}
+
+	RemoveDuplicates (colors);
+	CurrentDocument()->clearSelection();
+
+	for (LDObjectPtr obj : CurrentDocument()->objects())
+	{
+		if (colors.contains (obj->color()))
+			obj->select();
+	}
+}
+
+// =============================================================================
+//
+void MainWindow::actionSelectByType()
+{
+	if (Selection().isEmpty())
+		return;
+
+	QList<LDObjectType> types;
+	QStringList subfilenames;
+
+	for (LDObjectPtr obj : Selection())
+	{
+		types << obj->type();
+
+		if (types.last() == OBJ_Subfile)
+			subfilenames << obj.staticCast<LDSubfile>()->fileInfo()->name();
+	}
+
+	RemoveDuplicates (types);
+	RemoveDuplicates (subfilenames);
+	CurrentDocument()->clearSelection();
+
+	for (LDObjectPtr obj : CurrentDocument()->objects())
+	{
+		LDObjectType type = obj->type();
+
+		if (not types.contains (type))
+			continue;
+
+		// For subfiles, type check is not enough, we check the name of the document as well.
+		if (type == OBJ_Subfile and not subfilenames.contains (obj.staticCast<LDSubfile>()->fileInfo()->name()))
+			continue;
+
+		obj->select();
+	}
+}
+
+// =============================================================================
+//
+void MainWindow::actionGridCoarse()
+{
+	cfg::Grid = Grid::Coarse;
+	updateGridToolBar();
+}
+
+void MainWindow::actionGridMedium()
+{
+	cfg::Grid = Grid::Medium;
+	updateGridToolBar();
+}
+
+void MainWindow::actionGridFine()
+{
+	cfg::Grid = Grid::Fine;
+	updateGridToolBar();
+}
+
+// =============================================================================
+//
+void MainWindow::actionResetView()
+{
+	R()->resetAngles();
+	R()->update();
+}
+
+// =============================================================================
+//
+void MainWindow::actionInsertFrom()
+{
+	QString fname = QFileDialog::getOpenFileName();
+	int idx = getInsertionPoint();
+
+	if (not fname.length())
+		return;
+
+	QFile f (fname);
+
+	if (not f.open (QIODevice::ReadOnly))
+	{
+		Critical (format ("Couldn't open %1 (%2)", fname, f.errorString()));
+		return;
+	}
+
+	LDObjectList objs = LoadFileContents (&f, null);
+
+	CurrentDocument()->clearSelection();
+
+	for (LDObjectPtr obj : objs)
+	{
+		CurrentDocument()->insertObj (idx, obj);
+		obj->select();
+		R()->compileObject (obj);
+
+		idx++;
+	}
+
+	refresh();
+	scrollToSelection();
+}
+
+// =============================================================================
+//
+void MainWindow::actionExportTo()
+{
+	if (Selection().isEmpty())
+		return;
+
+	QString fname = QFileDialog::getSaveFileName();
+
+	if (fname.length() == 0)
+		return;
+
+	QFile file (fname);
+
+	if (not file.open (QIODevice::WriteOnly | QIODevice::Text))
+	{
+		Critical (format ("Unable to open %1 for writing (%2)", fname, file.errorString()));
+		return;
+	}
+
+	for (LDObjectPtr obj : Selection())
+	{
+		QString contents = obj->asText();
+		QByteArray data = contents.toUtf8();
+		file.write (data, data.size());
+		file.write ("\r\n", 2);
+	}
+}
+
+// =============================================================================
+//
+void MainWindow::actionInsertRaw()
+{
+	int idx = getInsertionPoint();
+
+	QDialog* const dlg = new QDialog;
+	QVBoxLayout* const layout = new QVBoxLayout;
+	QTextEdit* const te_edit = new QTextEdit;
+	QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+
+	layout->addWidget (te_edit);
+	layout->addWidget (bbx_buttons);
+	dlg->setLayout (layout);
+	dlg->setWindowTitle (APPNAME " - Insert Raw");
+	dlg->connect (bbx_buttons, SIGNAL (accepted()), dlg, SLOT (accept()));
+	dlg->connect (bbx_buttons, SIGNAL (rejected()), dlg, SLOT (reject()));
+
+	if (dlg->exec() == QDialog::Rejected)
+		return;
+
+	CurrentDocument()->clearSelection();
+
+	for (QString line : QString (te_edit->toPlainText()).split ("\n"))
+	{
+		LDObjectPtr obj = ParseLine (line);
+
+		CurrentDocument()->insertObj (idx, obj);
+		obj->select();
+		idx++;
+	}
+
+	refresh();
+	scrollToSelection();
+}
+
+// =============================================================================
+//
+void MainWindow::actionScreenshot()
+{
+	setlocale (LC_ALL, "C");
+
+	int w, h;
+	uchar* imgdata = R()->getScreencap (w, h);
+	QImage img = GetImageFromScreencap (imgdata, w, h);
+
+	QString root = Basename (CurrentDocument()->name());
+
+	if (root.right (4) == ".dat")
+		root.chop (4);
+
+	QString defaultname = (root.length() > 0) ? format ("%1.png", root) : "";
+	QString fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname,
+				"PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)");
+
+	if (not fname.isEmpty() and not img.save (fname))
+		Critical (format ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno)));
+
+	delete[] imgdata;
+}
+
+// =============================================================================
+//
+void MainWindow::actionAxes()
+{
+	cfg::DrawAxes = not cfg::DrawAxes;
+	updateActions();
+	R()->update();
+}
+
+// =============================================================================
+//
+void MainWindow::actionVisibilityToggle()
+{
+	for (LDObjectPtr obj : Selection())
+		obj->setHidden (not obj->isHidden());
+
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionVisibilityHide()
+{
+	for (LDObjectPtr obj : Selection())
+		obj->setHidden (true);
+
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionVisibilityReveal()
+{
+	for (LDObjectPtr obj : Selection())
+	obj->setHidden (false);
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionWireframe()
+{
+	cfg::DrawWireframe = not cfg::DrawWireframe;
+	R()->refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionSetOverlay()
+{
+	OverlayDialog dlg;
+
+	if (not dlg.exec())
+		return;
+
+	R()->setupOverlay ((ECamera) dlg.camera(), dlg.fpath(), dlg.ofsx(),
+		dlg.ofsy(), dlg.lwidth(), dlg.lheight());
+}
+
+// =============================================================================
+//
+void MainWindow::actionClearOverlay()
+{
+	R()->clearOverlay();
+}
+
+// =============================================================================
+//
+void MainWindow::actionModeSelect()
+{
+	R()->setEditMode (EditModeType::Select);
+}
+
+// =============================================================================
+//
+void MainWindow::actionModeDraw()
+{
+	R()->setEditMode (EditModeType::Draw);
+}
+
+// =============================================================================
+//
+void MainWindow::actionModeRectangle()
+{
+	R()->setEditMode (EditModeType::Rectangle);
+}
+
+// =============================================================================
+//
+void MainWindow::actionModeCircle()
+{
+	R()->setEditMode (EditModeType::Circle);
+}
+
+// =============================================================================
+//
+void MainWindow::actionModeMagicWand()
+{
+ 	R()->setEditMode (EditModeType::MagicWand);
+}
+
+void MainWindow::actionModeLinePath()
+{
+	R()->setEditMode (EditModeType::LinePath);
+}
+
+// =============================================================================
+//
+void MainWindow::actionDrawAngles()
+{
+	cfg::DrawAngles = not cfg::DrawAngles;
+	R()->refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionSetDrawDepth()
+{
+	if (R()->camera() == EFreeCamera)
+		return;
+
+	bool ok;
+	double depth = QInputDialog::getDouble (g_win, "Set Draw Depth",
+											format ("Depth value for %1 Camera:", R()->getCameraName()),
+											R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok);
+
+	if (ok)
+		R()->setDepthValue (depth);
+}
+
+#if 0
+// This is a test to draw a dummy axle. Meant to be used as a primitive gallery,
+// but I can't figure how to generate these pictures properly. Multi-threading
+// these is an immense pain.
+void MainWindow::actiontestpic()
+{
+	LDDocumentPtr file = getFile ("axle.dat");
+	setlocale (LC_ALL, "C");
+
+	if (not 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
+
+// =============================================================================
+//
+void MainWindow::actionScanPrimitives()
+{
+	PrimitiveScanner::start();
+}
+
+// =============================================================================
+//
+void MainWindow::actionBFCView()
+{
+	cfg::BFCRedGreenView = not cfg::BFCRedGreenView;
+
+	if (cfg::BFCRedGreenView)
+		cfg::RandomColors = false;
+
+	updateActions();
+	R()->refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionJumpTo()
+{
+	bool ok;
+	int defval = 0;
+	LDObjectPtr obj;
+
+	if (Selection().size() == 1)
+		defval = Selection()[0]->lineNumber();
+
+	int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval,
+		1, CurrentDocument()->getObjectCount(), 1, &ok);
+
+	if (not ok or (obj = CurrentDocument()->getObject (idx - 1)) == null)
+		return;
+
+	CurrentDocument()->clearSelection();
+	obj->select();
+	updateSelection();
+}
+
+// =============================================================================
+//
+void MainWindow::actionSubfileSelection()
+{
+	if (Selection().size() == 0)
+		return;
+
+	QString			parentpath (CurrentDocument()->fullPath());
+
+	// BFC type of the new subfile - it shall inherit the BFC type of the parent document
+	BFCStatement	bfctype (BFCStatement::NoCertify);
+
+	// Dirname of the new subfile
+	QString			subdirname (Dirname (parentpath));
+
+	// Title of the new subfile
+	QString			subtitle;
+
+	// Comment containing the title of the parent document
+	LDCommentPtr	titleobj (CurrentDocument()->getObject (0).dynamicCast<LDComment>());
+
+	// License text for the subfile
+	QString			license (PreferredLicenseText());
+
+	// LDraw code body of the new subfile (i.e. code of the selection)
+	QStringList		code;
+
+	// Full path of the subfile to be
+	QString			fullsubname;
+
+	// Where to insert the subfile reference?
+	int				refidx (Selection()[0]->lineNumber());
+
+	// Determine title of subfile
+	if (titleobj != null)
+		subtitle = "~" + titleobj->text();
+	else
+		subtitle = "~subfile";
+
+	// Remove duplicate tildes
+	while (subtitle.startsWith ("~~"))
+		subtitle.remove (0, 1);
+
+	// If this the parent document isn't already in s/, we need to stuff it into
+	// a subdirectory named s/. Ensure it exists!
+	QString topdirname = Basename (Dirname (CurrentDocument()->fullPath()));
+
+	if (topdirname != "s")
+	{
+		QString desiredPath = subdirname + "/s";
+		QString title = tr ("Create subfile directory?");
+		QString text = format (tr ("The directory <b>%1</b> is suggested for "
+			"subfiles. This directory does not exist, create it?"), desiredPath);
+
+		if (QDir (desiredPath).exists() or Confirm (title, text))
+		{
+			subdirname = desiredPath;
+			QDir().mkpath (subdirname);
+		}
+		else
+			return;
+	}
+
+	// Determine the body of the name of the subfile
+	if (not parentpath.isEmpty())
+	{
+		// Chop existing '.dat' suffix
+		if (parentpath.endsWith (".dat"))
+			parentpath.chop (4);
+
+		// Remove the s?? suffix if it's there, otherwise we'll get filenames
+		// like s01s01.dat when subfiling subfiles.
+		QRegExp subfilesuffix ("s[0-9][0-9]$");
+		if (subfilesuffix.indexIn (parentpath) != -1)
+			parentpath.chop (subfilesuffix.matchedLength());
+
+		int subidx = 1;
+		QString digits;
+		QFile f;
+		QString testfname;
+
+		// Now find the appropriate filename. Increase the number of the subfile
+		// until we find a name which isn't already taken.
+		do
+		{
+			digits.setNum (subidx++);
+
+			// pad it with a zero
+			if (digits.length() == 1)
+				digits.prepend ("0");
+
+			fullsubname = subdirname + "/" + Basename (parentpath) + "s" + digits + ".dat";
+		} while (FindDocument ("s\\" + Basename (fullsubname)) != null or QFile (fullsubname).exists());
+	}
+
+	// Determine the BFC winding type used in the main document - it is to
+	// be carried over to the subfile.
+	LDIterate<LDBFC> (CurrentDocument()->objects(), [&] (LDBFCPtr const& bfc)
+	{
+		if (Eq (bfc->statement(), BFCStatement::CertifyCCW, BFCStatement::CertifyCW, 
+			BFCStatement::NoCertify))
+		{
+			bfctype = bfc->statement();
+			Break();
+		}
+	});
+
+	// Get the body of the document in LDraw code
+	for (LDObjectPtr obj : Selection())
+		code << obj->asText();
+
+	// Create the new subfile document
+	LDDocumentPtr doc = LDDocument::createNew();
+	doc->setImplicit (false);
+	doc->setFullPath (fullsubname);
+	doc->setName (LDDocument::shortenName (fullsubname));
+
+	LDObjectList objs;
+	objs << LDSpawn<LDComment> (subtitle);
+	objs << LDSpawn<LDComment> ("Name: "); // This gets filled in when the subfile is saved
+	objs << LDSpawn<LDComment> (format ("Author: %1 [%2]", cfg::DefaultName, cfg::DefaultUser));
+	objs << LDSpawn<LDComment> ("!LDRAW_ORG Unofficial_Subpart");
+
+	if (not license.isEmpty())
+		objs << LDSpawn<LDComment> (license);
+
+	objs << LDSpawn<LDEmpty>();
+	objs << LDSpawn<LDBFC> (bfctype);
+	objs << LDSpawn<LDEmpty>();
+
+	doc->addObjects (objs);
+
+	// Add the actual subfile code to the new document
+	for (QString line : code)
+	{
+		LDObjectPtr obj = ParseLine (line);
+		doc->addObject (obj);
+	}
+
+	// Try save it
+	if (save (doc, true))
+	{
+		// Save was successful. Delete the original selection now from the
+		// main document.
+		for (LDObjectPtr obj : Selection())
+			obj->destroy();
+
+		// Add a reference to the new subfile to where the selection was
+		LDSubfilePtr ref (LDSpawn<LDSubfile>());
+		ref->setColor (MainColor());
+		ref->setFileInfo (doc);
+		ref->setPosition (Origin);
+		ref->setTransform (IdentityMatrix);
+		CurrentDocument()->insertObj (refidx, ref);
+
+		// Refresh stuff
+		updateDocumentList();
+		doFullRefresh();
+	}
+	else
+	{
+		// Failed to save.
+		doc->dismiss();
+	}
+}
+
+void MainWindow::actionRandomColors()
+{
+	cfg::RandomColors = not cfg::RandomColors;
+
+	if (cfg::RandomColors)
+		cfg::BFCRedGreenView = false;
+
+	updateActions();
+	R()->refresh();
+}
+
+void MainWindow::actionOpenSubfiles()
+{
+	for (LDObjectPtr obj : Selection())
+	{
+		LDSubfilePtr ref = obj.dynamicCast<LDSubfile>();
+
+		if (ref == null or not ref->fileInfo()->isImplicit())
+			continue;
+
+		ref->fileInfo()->setImplicit (false);
+	}
+}
+
+void MainWindow::actionDrawSurfaces()
+{
+	cfg::DrawSurfaces = not cfg::DrawSurfaces;
+	updateActions();
+	update();
+}
+
+void MainWindow::actionDrawEdgeLines()
+{
+	cfg::DrawEdgeLines = not cfg::DrawEdgeLines;
+	updateActions();
+	update();
+}
+
+void MainWindow::actionDrawConditionalLines()
+{
+	cfg::DrawConditionalLines = not cfg::DrawConditionalLines;
+	updateActions();
+	update();
+}
--- a/src/actionsEdit.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,738 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu 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 <limits>
-#include <QSpinBox>
-#include <QCheckBox>
-#include <QBoxLayout>
-#include <QClipboard>
-#include <QInputDialog>
-#include "mainWindow.h"
-#include "main.h"
-#include "ldDocument.h"
-#include "colorSelector.h"
-#include "miscallenous.h"
-#include "radioGroup.h"
-#include "glRenderer.h"
-#include "dialogs.h"
-#include "colors.h"
-#include "ldObjectMath.h"
-#include "ui_replcoords.h"
-#include "ui_editraw.h"
-#include "ui_flip.h"
-#include "ui_addhistoryline.h"
-
-EXTERN_CFGENTRY (String, DefaultUser)
-
-CFGENTRY (Int, RoundPosition,		3)
-CFGENTRY (Int, RoundMatrix,		4)
-CFGENTRY (Int, SplitLinesSegments, 5)
-
-// =============================================================================
-//
-static int CopyToClipboard()
-{
-	LDObjectList objs = Selection();
-	int num = 0;
-
-	// Clear the clipboard first.
-	qApp->clipboard()->clear();
-
-	// Now, copy the contents into the clipboard.
-	QString data;
-
-	for (LDObjectPtr obj : objs)
-	{
-		if (not data.isEmpty())
-			data += "\n";
-
-		data += obj->asText();
-		++num;
-	}
-
-	qApp->clipboard()->setText (data);
-	return num;
-}
-
-// =============================================================================
-//
-void MainWindow::actionCut()
-{
-	int num = CopyToClipboard();
-	deleteSelection();
-	print (tr ("%1 objects cut"), num);
-}
-
-// =============================================================================
-//
-void MainWindow::actionCopy()
-{
-	int num = CopyToClipboard();
-	print (tr ("%1 objects copied"), num);
-}
-
-// =============================================================================
-//
-void MainWindow::actionPaste()
-{
-	const QString clipboardText = qApp->clipboard()->text();
-	int idx = getInsertionPoint();
-	CurrentDocument()->clearSelection();
-	int num = 0;
-
-	for (QString line : clipboardText.split ("\n"))
-	{
-		LDObjectPtr pasted = ParseLine (line);
-		CurrentDocument()->insertObj (idx++, pasted);
-		pasted->select();
-		++num;
-	}
-
-	print (tr ("%1 objects pasted"), num);
-	refresh();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-void MainWindow::actionDelete()
-{
-	int num = deleteSelection();
-	print (tr ("%1 objects deleted"), num);
-}
-
-// =============================================================================
-//
-static void DoInline (bool deep)
-{
-	LDObjectList sel = Selection();
-
-	LDIterate<LDSubfile> (Selection(), [&](LDSubfilePtr const& ref)
-	{
-		// Get the index of the subfile so we know where to insert the
-		// inlined contents.
-		long idx = ref->lineNumber();
-
-		assert (idx != -1);
-		LDObjectList objs = ref->inlineContents (deep, false);
-
-		// Merge in the inlined objects
-		for (LDObjectPtr inlineobj : objs)
-		{
-			QString line = inlineobj->asText();
-			inlineobj->destroy();
-			LDObjectPtr newobj = ParseLine (line);
-			CurrentDocument()->insertObj (idx++, newobj);
-			newobj->select();
-		}
-
-		// Delete the subfile now as it's been inlined.
-		ref->destroy();
-	});
-
-	g_win->refresh();
-}
-
-void MainWindow::actionInline()
-{
-	DoInline (false);
-}
-
-void MainWindow::actionInlineDeep()
-{
-	DoInline (true);
-}
-
-// =============================================================================
-//
-void MainWindow::actionSplitQuads()
-{
-	int num = 0;
-
-	LDIterate<LDQuad> (Selection(), [&](LDQuadPtr const& quad)
-	{
-		// Find the index of this quad
-		long index = quad->lineNumber();
-
-		if (index == -1)
-			return;
-
-		QList<LDTrianglePtr> triangles = quad->splitToTriangles();
-
-		// Replace the quad with the first triangle and add the second triangle
-		// after the first one.
-		CurrentDocument()->setObject (index, triangles[0]);
-		CurrentDocument()->insertObj (index + 1, triangles[1]);
-		num++;
-	});
-
-	print ("%1 quadrilaterals split", num);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionEditRaw()
-{
-	if (Selection().size() != 1)
-		return;
-
-	LDObjectPtr obj = Selection()[0];
-	QDialog* dlg = new QDialog;
-	Ui::EditRawUI ui;
-
-	ui.setupUi (dlg);
-	ui.code->setText (obj->asText());
-
-	if (obj->type() == OBJ_Error)
-		ui.errorDescription->setText (obj.staticCast<LDError>()->reason());
-	else
-	{
-		ui.errorDescription->hide();
-		ui.errorIcon->hide();
-	}
-
-	if (dlg->exec() == QDialog::Rejected)
-		return;
-
-	// Reinterpret it from the text of the input field
-	LDObjectPtr newobj = ParseLine (ui.code->text());
-	obj->replace (newobj);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionSetColor()
-{
-	if (Selection().isEmpty())
-		return;
-
-	LDObjectList objs = Selection();
-
-	// If all selected objects have the same color, said color is our default
-	// value to the color selection dialog.
-	LDColor color;
-	LDColor defaultcol = getSelectedColor();
-
-	// Show the dialog to the user now and ask for a color.
-	if (ColorSelector::selectColor (color, defaultcol, g_win))
-	{
-		for (LDObjectPtr obj : objs)
-		{
-			if (obj->isColored())
-				obj->setColor (color);
-		}
-
-		refresh();
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::actionBorders()
-{
-	LDObjectList objs = Selection();
-	int num = 0;
-
-	for (LDObjectPtr obj : objs)
-	{
-		const LDObjectType type = obj->type();
-
-		if (type != OBJ_Quad and type != OBJ_Triangle)
-			continue;
-
-		LDLinePtr lines[4];
-
-		if (type == OBJ_Quad)
-		{
-			LDQuadPtr quad = obj.staticCast<LDQuad>();
-			lines[0] = LDSpawn<LDLine> (quad->vertex (0), quad->vertex (1));
-			lines[1] = LDSpawn<LDLine> (quad->vertex (1), quad->vertex (2));
-			lines[2] = LDSpawn<LDLine> (quad->vertex (2), quad->vertex (3));
-			lines[3] = LDSpawn<LDLine> (quad->vertex (3), quad->vertex (0));
-		}
-		else
-		{
-			LDTrianglePtr tri = obj.staticCast<LDTriangle>();
-			lines[0] = LDSpawn<LDLine> (tri->vertex (0), tri->vertex (1));
-			lines[1] = LDSpawn<LDLine> (tri->vertex (1), tri->vertex (2));
-			lines[2] = LDSpawn<LDLine> (tri->vertex (2), tri->vertex (0));
-		}
-
-		for (int i = 0; i < countof (lines); ++i)
-		{
-			if (lines[i] == null)
-				continue;
-
-			long idx = obj->lineNumber() + i + 1;
-			CurrentDocument()->insertObj (idx, lines[i]);
-		}
-
-		num += countof (lines);
-	}
-
-	print (tr ("Added %1 border lines"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-static void MoveSelection (const bool up)
-{
-	LDObjectList objs = Selection();
-	LDObject::moveObjects (objs, up);
-	g_win->buildObjList();
-}
-
-// =============================================================================
-//
-void MainWindow::actionMoveUp()
-{
-	MoveSelection (true);
-}
-
-void MainWindow::actionMoveDown()
-{
-	MoveSelection (false);
-}
-
-// =============================================================================
-//
-void MainWindow::actionUndo()
-{
-	CurrentDocument()->undo();
-}
-
-void MainWindow::actionRedo()
-{
-	CurrentDocument()->redo();
-}
-
-// =============================================================================
-//
-static void MoveObjects (Vertex vect)
-{
-	// Apply the grid values
-	vect *= *CurrentGrid().coordinateSnap;
-
-	for (LDObjectPtr obj : Selection())
-		obj->move (vect);
-
-	g_win->refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionMoveXNeg()
-{
-	MoveObjects ({-1, 0, 0});
-}
-
-void MainWindow::actionMoveYNeg()
-{
-	MoveObjects ({0, -1, 0});
-}
-
-void MainWindow::actionMoveZNeg()
-{
-	MoveObjects ({0, 0, -1});
-}
-
-void MainWindow::actionMoveXPos()
-{
-	MoveObjects ({1, 0, 0});
-}
-
-void MainWindow::actionMoveYPos()
-{
-	MoveObjects ({0, 1, 0});
-}
-
-void MainWindow::actionMoveZPos()
-{
-	MoveObjects ({0, 0, 1});
-}
-
-// =============================================================================
-//
-void MainWindow::actionInvert()
-{
-	for (LDObjectPtr obj : Selection())
-		obj->invert();
-
-	refresh();
-}
-
-// =============================================================================
-//
-static double GetRotateActionAngle()
-{
-	return (Pi * *CurrentGrid().angleSnap) / 180;
-}
-
-void MainWindow::actionRotateXPos()
-{
-	RotateObjects (1, 0, 0, GetRotateActionAngle(), Selection());
-}
-void MainWindow::actionRotateYPos()
-{
-	RotateObjects (0, 1, 0, GetRotateActionAngle(), Selection());
-}
-void MainWindow::actionRotateZPos()
-{
-	RotateObjects (0, 0, 1, GetRotateActionAngle(), Selection());
-}
-void MainWindow::actionRotateXNeg()
-{
-	RotateObjects (-1, 0, 0, GetRotateActionAngle(), Selection());
-}
-void MainWindow::actionRotateYNeg()
-{
-	RotateObjects (0, -1, 0, GetRotateActionAngle(), Selection());
-}
-void MainWindow::actionRotateZNeg()
-{
-	RotateObjects (0, 0, -1, GetRotateActionAngle(), Selection());
-}
-
-void MainWindow::actionRotationPoint()
-{
-	ConfigureRotationPoint();
-}
-
-// =============================================================================
-//
-void MainWindow::actionRoundCoordinates()
-{
-	setlocale (LC_ALL, "C");
-	int num = 0;
-
-	for (LDObjectPtr obj : Selection())
-	{
-		LDMatrixObjectPtr mo = obj.dynamicCast<LDMatrixObject>();
-
-		if (mo != null)
-		{
-			Vertex v = mo->position();
-			Matrix t = mo->transform();
-
-			// Note: matrix values are to be rounded to 4 decimals.
-			v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
-			ApplyToMatrix (t, [](int, double& a) { RoundToDecimals (a, cfg::RoundMatrix); });
-
-			mo->setPosition (v);
-			mo->setTransform (t);
-			num += 12;
-		}
-		else
-		{
-			for (int i = 0; i < obj->numVertices(); ++i)
-			{
-				Vertex v = obj->vertex (i);
-				v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
-				obj->setVertex (i, v);
-				num += 3;
-			}
-		}
-	}
-
-	print (tr ("Rounded %1 values"), num);
-	refreshObjectList();
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionUncolor()
-{
-	int num = 0;
-
-	for (LDObjectPtr obj : Selection())
-	{
-		if (not obj->isColored())
-			continue;
-
-		obj->setColor (obj->defaultColor());
-		num++;
-	}
-
-	print (tr ("%1 objects uncolored"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionReplaceCoords()
-{
-	QDialog* dlg = new QDialog (g_win);
-	Ui::ReplaceCoordsUI ui;
-	ui.setupUi (dlg);
-
-	if (not 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 (LDObjectPtr obj : Selection())
-	{
-		for (int i = 0; i < obj->numVertices(); ++i)
-		{
-			Vertex v = obj->vertex (i);
-
-			v.apply ([&](Axis ax, double& coord)
-			{
-				if (not sel.contains (ax) or
-					(not any and coord != search))
-				{
-					return;
-				}
-
-				if (not rel)
-					coord = 0;
-
-				coord += replacement;
-				num++;
-			});
-
-			obj->setVertex (i, v);
-		}
-	}
-
-	print (tr ("Altered %1 values"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionFlip()
-{
-	QDialog* dlg = new QDialog;
-	Ui::FlipUI ui;
-	ui.setupUi (dlg);
-
-	if (not 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 (LDObjectPtr obj : Selection())
-	{
-		for (int i = 0; i < obj->numVertices(); ++i)
-		{
-			Vertex v = obj->vertex (i);
-
-			v.apply ([&](Axis ax, double& a)
-			{
-				if (sel.contains (ax))
-					a = -a;
-			});
-
-			obj->setVertex (i, v);
-		}
-	}
-
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionDemote()
-{
-	int num = 0;
-
-	LDIterate<LDCondLine> (Selection(), [&](LDCondLinePtr const& cnd)
-	{
-		cnd->toEdgeLine();
-		++num;
-	});
-
-	print (tr ("Demoted %1 conditional lines"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-static bool IsColorUsed (LDColor color)
-{
-	for (LDObjectPtr obj : CurrentDocument()->objects())
-	{
-		if (obj->isColored() and obj->color() == color)
-			return true;
-	}
-
-	return false;
-}
-
-// =============================================================================
-//
-void MainWindow::actionAutocolor()
-{
-	int colnum = 0;
-	LDColor color;
-
-	for (colnum = 0;
-		 colnum < CountLDConfigColors() and
-			((color = LDColor::fromIndex (colnum)) == null or
-			IsColorUsed (color));
-		colnum++) {}
-
-	if (colnum >= CountLDConfigColors())
-	{
-		print (tr ("Cannot auto-color: all colors are in use!"));
-		return;
-	}
-
-	for (LDObjectPtr obj : Selection())
-	{
-		if (not obj->isColored())
-			continue;
-
-		obj->setColor (color);
-	}
-
-	print (tr ("Auto-colored: new color is [%1] %2"), colnum, color.name());
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::actionAddHistoryLine()
-{
-	LDObjectPtr obj;
-	bool ishistory = false,
-		 prevIsHistory = false;
-
-	QDialog* dlg = new QDialog;
-	Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
-	ui->setupUi (dlg);
-	ui->m_username->setText (cfg::DefaultUser);
-	ui->m_date->setDate (QDate::currentDate());
-	ui->m_comment->setFocus();
-
-	if (not dlg->exec())
-		return;
-
-	// Create the comment object based on input
-	QString commentText = format ("!HISTORY %1 [%2] %3",
-		ui->m_date->date().toString ("yyyy-MM-dd"),
-		ui->m_username->text(),
-		ui->m_comment->text());
-
-	LDCommentPtr comm (LDSpawn<LDComment> (commentText));
-
-	// Find a spot to place the new comment
-	for (obj = CurrentDocument()->getObject (0);
-		obj != null and obj->next() != null and not obj->next()->isScemantic();
-		obj = obj->next())
-	{
-		LDCommentPtr comm = obj.dynamicCast<LDComment>();
-
-		if (comm != null and comm->text().startsWith ("!HISTORY "))
-			ishistory = true;
-
-		if (prevIsHistory and not ishistory)
-		{
-			// Last line was history, this isn't, thus insert the new history
-			// line here.
-			break;
-		}
-
-		prevIsHistory = ishistory;
-	}
-
-	int idx = obj ? obj->lineNumber() : 0;
-	CurrentDocument()->insertObj (idx++, comm);
-
-	// If we're adding a history line right before a scemantic object, pad it
-	// an empty line
-	if (obj and obj->next() and obj->next()->isScemantic())
-		CurrentDocument()->insertObj (idx, LDEmptyPtr (LDSpawn<LDEmpty>()));
-
-	buildObjList();
-	delete ui;
-}
-
-void MainWindow::actionSplitLines()
-{
-	bool ok;
-	int segments = QInputDialog::getInt (g_win, APPNAME, "Amount of segments:", cfg::SplitLinesSegments, 0,
-		std::numeric_limits<int>::max(), 1, &ok);
-
-	if (not ok)
-		return;
-
-	cfg::SplitLinesSegments = segments;
-
-	for (LDObjectPtr obj : Selection())
-	{
-		if (not Eq (obj->type(), OBJ_Line, OBJ_CondLine))
-			continue;
-
-		QVector<LDObjectPtr> newsegs;
-
-		for (int i = 0; i < segments; ++i)
-		{
-			LDObjectPtr segment;
-			Vertex v0, v1;
-
-			v0.apply ([&](Axis ax, double& a)
-			{
-				double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
-				a = (obj->vertex (0)[ax] + ((len * i) / segments));
-			});
-
-			v1.apply ([&](Axis ax, double& a)
-			{
-				double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
-				a = (obj->vertex (0)[ax] + ((len * (i + 1)) / segments));
-			});
-
-			if (obj->type() == OBJ_Line)
-				segment = LDSpawn<LDLine> (v0, v1);
-			else
-				segment = LDSpawn<LDCondLine> (v0, v1, obj->vertex (2), obj->vertex (3));
-
-			newsegs << segment;
-		}
-
-		int ln = obj->lineNumber();
-
-		for (LDObjectPtr seg : newsegs)
-			CurrentDocument()->insertObj (ln++, seg);
-
-		obj->destroy();
-	}
-
-	buildObjList();
-	g_win->refresh();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/actionsEdit.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,738 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu 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 <limits>
+#include <QSpinBox>
+#include <QCheckBox>
+#include <QBoxLayout>
+#include <QClipboard>
+#include <QInputDialog>
+#include "mainWindow.h"
+#include "main.h"
+#include "ldDocument.h"
+#include "colorSelector.h"
+#include "miscallenous.h"
+#include "radioGroup.h"
+#include "glRenderer.h"
+#include "dialogs.h"
+#include "colors.h"
+#include "ldObjectMath.h"
+#include "ui_replcoords.h"
+#include "ui_editraw.h"
+#include "ui_flip.h"
+#include "ui_addhistoryline.h"
+
+EXTERN_CFGENTRY (String, DefaultUser)
+
+CFGENTRY (Int, RoundPosition,		3)
+CFGENTRY (Int, RoundMatrix,		4)
+CFGENTRY (Int, SplitLinesSegments, 5)
+
+// =============================================================================
+//
+static int CopyToClipboard()
+{
+	LDObjectList objs = Selection();
+	int num = 0;
+
+	// Clear the clipboard first.
+	qApp->clipboard()->clear();
+
+	// Now, copy the contents into the clipboard.
+	QString data;
+
+	for (LDObjectPtr obj : objs)
+	{
+		if (not data.isEmpty())
+			data += "\n";
+
+		data += obj->asText();
+		++num;
+	}
+
+	qApp->clipboard()->setText (data);
+	return num;
+}
+
+// =============================================================================
+//
+void MainWindow::actionCut()
+{
+	int num = CopyToClipboard();
+	deleteSelection();
+	print (tr ("%1 objects cut"), num);
+}
+
+// =============================================================================
+//
+void MainWindow::actionCopy()
+{
+	int num = CopyToClipboard();
+	print (tr ("%1 objects copied"), num);
+}
+
+// =============================================================================
+//
+void MainWindow::actionPaste()
+{
+	const QString clipboardText = qApp->clipboard()->text();
+	int idx = getInsertionPoint();
+	CurrentDocument()->clearSelection();
+	int num = 0;
+
+	for (QString line : clipboardText.split ("\n"))
+	{
+		LDObjectPtr pasted = ParseLine (line);
+		CurrentDocument()->insertObj (idx++, pasted);
+		pasted->select();
+		++num;
+	}
+
+	print (tr ("%1 objects pasted"), num);
+	refresh();
+	scrollToSelection();
+}
+
+// =============================================================================
+//
+void MainWindow::actionDelete()
+{
+	int num = deleteSelection();
+	print (tr ("%1 objects deleted"), num);
+}
+
+// =============================================================================
+//
+static void DoInline (bool deep)
+{
+	LDObjectList sel = Selection();
+
+	LDIterate<LDSubfile> (Selection(), [&](LDSubfilePtr const& ref)
+	{
+		// Get the index of the subfile so we know where to insert the
+		// inlined contents.
+		long idx = ref->lineNumber();
+
+		assert (idx != -1);
+		LDObjectList objs = ref->inlineContents (deep, false);
+
+		// Merge in the inlined objects
+		for (LDObjectPtr inlineobj : objs)
+		{
+			QString line = inlineobj->asText();
+			inlineobj->destroy();
+			LDObjectPtr newobj = ParseLine (line);
+			CurrentDocument()->insertObj (idx++, newobj);
+			newobj->select();
+		}
+
+		// Delete the subfile now as it's been inlined.
+		ref->destroy();
+	});
+
+	g_win->refresh();
+}
+
+void MainWindow::actionInline()
+{
+	DoInline (false);
+}
+
+void MainWindow::actionInlineDeep()
+{
+	DoInline (true);
+}
+
+// =============================================================================
+//
+void MainWindow::actionSplitQuads()
+{
+	int num = 0;
+
+	LDIterate<LDQuad> (Selection(), [&](LDQuadPtr const& quad)
+	{
+		// Find the index of this quad
+		long index = quad->lineNumber();
+
+		if (index == -1)
+			return;
+
+		QList<LDTrianglePtr> triangles = quad->splitToTriangles();
+
+		// Replace the quad with the first triangle and add the second triangle
+		// after the first one.
+		CurrentDocument()->setObject (index, triangles[0]);
+		CurrentDocument()->insertObj (index + 1, triangles[1]);
+		num++;
+	});
+
+	print ("%1 quadrilaterals split", num);
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionEditRaw()
+{
+	if (Selection().size() != 1)
+		return;
+
+	LDObjectPtr obj = Selection()[0];
+	QDialog* dlg = new QDialog;
+	Ui::EditRawUI ui;
+
+	ui.setupUi (dlg);
+	ui.code->setText (obj->asText());
+
+	if (obj->type() == OBJ_Error)
+		ui.errorDescription->setText (obj.staticCast<LDError>()->reason());
+	else
+	{
+		ui.errorDescription->hide();
+		ui.errorIcon->hide();
+	}
+
+	if (dlg->exec() == QDialog::Rejected)
+		return;
+
+	// Reinterpret it from the text of the input field
+	LDObjectPtr newobj = ParseLine (ui.code->text());
+	obj->replace (newobj);
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionSetColor()
+{
+	if (Selection().isEmpty())
+		return;
+
+	LDObjectList objs = Selection();
+
+	// If all selected objects have the same color, said color is our default
+	// value to the color selection dialog.
+	LDColor color;
+	LDColor defaultcol = getSelectedColor();
+
+	// Show the dialog to the user now and ask for a color.
+	if (ColorSelector::selectColor (color, defaultcol, g_win))
+	{
+		for (LDObjectPtr obj : objs)
+		{
+			if (obj->isColored())
+				obj->setColor (color);
+		}
+
+		refresh();
+	}
+}
+
+// =============================================================================
+//
+void MainWindow::actionBorders()
+{
+	LDObjectList objs = Selection();
+	int num = 0;
+
+	for (LDObjectPtr obj : objs)
+	{
+		const LDObjectType type = obj->type();
+
+		if (type != OBJ_Quad and type != OBJ_Triangle)
+			continue;
+
+		LDLinePtr lines[4];
+
+		if (type == OBJ_Quad)
+		{
+			LDQuadPtr quad = obj.staticCast<LDQuad>();
+			lines[0] = LDSpawn<LDLine> (quad->vertex (0), quad->vertex (1));
+			lines[1] = LDSpawn<LDLine> (quad->vertex (1), quad->vertex (2));
+			lines[2] = LDSpawn<LDLine> (quad->vertex (2), quad->vertex (3));
+			lines[3] = LDSpawn<LDLine> (quad->vertex (3), quad->vertex (0));
+		}
+		else
+		{
+			LDTrianglePtr tri = obj.staticCast<LDTriangle>();
+			lines[0] = LDSpawn<LDLine> (tri->vertex (0), tri->vertex (1));
+			lines[1] = LDSpawn<LDLine> (tri->vertex (1), tri->vertex (2));
+			lines[2] = LDSpawn<LDLine> (tri->vertex (2), tri->vertex (0));
+		}
+
+		for (int i = 0; i < countof (lines); ++i)
+		{
+			if (lines[i] == null)
+				continue;
+
+			long idx = obj->lineNumber() + i + 1;
+			CurrentDocument()->insertObj (idx, lines[i]);
+		}
+
+		num += countof (lines);
+	}
+
+	print (tr ("Added %1 border lines"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+static void MoveSelection (const bool up)
+{
+	LDObjectList objs = Selection();
+	LDObject::moveObjects (objs, up);
+	g_win->buildObjList();
+}
+
+// =============================================================================
+//
+void MainWindow::actionMoveUp()
+{
+	MoveSelection (true);
+}
+
+void MainWindow::actionMoveDown()
+{
+	MoveSelection (false);
+}
+
+// =============================================================================
+//
+void MainWindow::actionUndo()
+{
+	CurrentDocument()->undo();
+}
+
+void MainWindow::actionRedo()
+{
+	CurrentDocument()->redo();
+}
+
+// =============================================================================
+//
+static void MoveObjects (Vertex vect)
+{
+	// Apply the grid values
+	vect *= *CurrentGrid().coordinateSnap;
+
+	for (LDObjectPtr obj : Selection())
+		obj->move (vect);
+
+	g_win->refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionMoveXNeg()
+{
+	MoveObjects ({-1, 0, 0});
+}
+
+void MainWindow::actionMoveYNeg()
+{
+	MoveObjects ({0, -1, 0});
+}
+
+void MainWindow::actionMoveZNeg()
+{
+	MoveObjects ({0, 0, -1});
+}
+
+void MainWindow::actionMoveXPos()
+{
+	MoveObjects ({1, 0, 0});
+}
+
+void MainWindow::actionMoveYPos()
+{
+	MoveObjects ({0, 1, 0});
+}
+
+void MainWindow::actionMoveZPos()
+{
+	MoveObjects ({0, 0, 1});
+}
+
+// =============================================================================
+//
+void MainWindow::actionInvert()
+{
+	for (LDObjectPtr obj : Selection())
+		obj->invert();
+
+	refresh();
+}
+
+// =============================================================================
+//
+static double GetRotateActionAngle()
+{
+	return (Pi * *CurrentGrid().angleSnap) / 180;
+}
+
+void MainWindow::actionRotateXPos()
+{
+	RotateObjects (1, 0, 0, GetRotateActionAngle(), Selection());
+}
+void MainWindow::actionRotateYPos()
+{
+	RotateObjects (0, 1, 0, GetRotateActionAngle(), Selection());
+}
+void MainWindow::actionRotateZPos()
+{
+	RotateObjects (0, 0, 1, GetRotateActionAngle(), Selection());
+}
+void MainWindow::actionRotateXNeg()
+{
+	RotateObjects (-1, 0, 0, GetRotateActionAngle(), Selection());
+}
+void MainWindow::actionRotateYNeg()
+{
+	RotateObjects (0, -1, 0, GetRotateActionAngle(), Selection());
+}
+void MainWindow::actionRotateZNeg()
+{
+	RotateObjects (0, 0, -1, GetRotateActionAngle(), Selection());
+}
+
+void MainWindow::actionRotationPoint()
+{
+	ConfigureRotationPoint();
+}
+
+// =============================================================================
+//
+void MainWindow::actionRoundCoordinates()
+{
+	setlocale (LC_ALL, "C");
+	int num = 0;
+
+	for (LDObjectPtr obj : Selection())
+	{
+		LDMatrixObjectPtr mo = obj.dynamicCast<LDMatrixObject>();
+
+		if (mo != null)
+		{
+			Vertex v = mo->position();
+			Matrix t = mo->transform();
+
+			// Note: matrix values are to be rounded to 4 decimals.
+			v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
+			ApplyToMatrix (t, [](int, double& a) { RoundToDecimals (a, cfg::RoundMatrix); });
+
+			mo->setPosition (v);
+			mo->setTransform (t);
+			num += 12;
+		}
+		else
+		{
+			for (int i = 0; i < obj->numVertices(); ++i)
+			{
+				Vertex v = obj->vertex (i);
+				v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
+				obj->setVertex (i, v);
+				num += 3;
+			}
+		}
+	}
+
+	print (tr ("Rounded %1 values"), num);
+	refreshObjectList();
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionUncolor()
+{
+	int num = 0;
+
+	for (LDObjectPtr obj : Selection())
+	{
+		if (not obj->isColored())
+			continue;
+
+		obj->setColor (obj->defaultColor());
+		num++;
+	}
+
+	print (tr ("%1 objects uncolored"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionReplaceCoords()
+{
+	QDialog* dlg = new QDialog (g_win);
+	Ui::ReplaceCoordsUI ui;
+	ui.setupUi (dlg);
+
+	if (not 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 (LDObjectPtr obj : Selection())
+	{
+		for (int i = 0; i < obj->numVertices(); ++i)
+		{
+			Vertex v = obj->vertex (i);
+
+			v.apply ([&](Axis ax, double& coord)
+			{
+				if (not sel.contains (ax) or
+					(not any and coord != search))
+				{
+					return;
+				}
+
+				if (not rel)
+					coord = 0;
+
+				coord += replacement;
+				num++;
+			});
+
+			obj->setVertex (i, v);
+		}
+	}
+
+	print (tr ("Altered %1 values"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionFlip()
+{
+	QDialog* dlg = new QDialog;
+	Ui::FlipUI ui;
+	ui.setupUi (dlg);
+
+	if (not 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 (LDObjectPtr obj : Selection())
+	{
+		for (int i = 0; i < obj->numVertices(); ++i)
+		{
+			Vertex v = obj->vertex (i);
+
+			v.apply ([&](Axis ax, double& a)
+			{
+				if (sel.contains (ax))
+					a = -a;
+			});
+
+			obj->setVertex (i, v);
+		}
+	}
+
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionDemote()
+{
+	int num = 0;
+
+	LDIterate<LDCondLine> (Selection(), [&](LDCondLinePtr const& cnd)
+	{
+		cnd->toEdgeLine();
+		++num;
+	});
+
+	print (tr ("Demoted %1 conditional lines"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+static bool IsColorUsed (LDColor color)
+{
+	for (LDObjectPtr obj : CurrentDocument()->objects())
+	{
+		if (obj->isColored() and obj->color() == color)
+			return true;
+	}
+
+	return false;
+}
+
+// =============================================================================
+//
+void MainWindow::actionAutocolor()
+{
+	int colnum = 0;
+	LDColor color;
+
+	for (colnum = 0;
+		 colnum < CountLDConfigColors() and
+			((color = LDColor::fromIndex (colnum)) == null or
+			IsColorUsed (color));
+		colnum++) {}
+
+	if (colnum >= CountLDConfigColors())
+	{
+		print (tr ("Cannot auto-color: all colors are in use!"));
+		return;
+	}
+
+	for (LDObjectPtr obj : Selection())
+	{
+		if (not obj->isColored())
+			continue;
+
+		obj->setColor (color);
+	}
+
+	print (tr ("Auto-colored: new color is [%1] %2"), colnum, color.name());
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::actionAddHistoryLine()
+{
+	LDObjectPtr obj;
+	bool ishistory = false,
+		 prevIsHistory = false;
+
+	QDialog* dlg = new QDialog;
+	Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
+	ui->setupUi (dlg);
+	ui->m_username->setText (cfg::DefaultUser);
+	ui->m_date->setDate (QDate::currentDate());
+	ui->m_comment->setFocus();
+
+	if (not dlg->exec())
+		return;
+
+	// Create the comment object based on input
+	QString commentText = format ("!HISTORY %1 [%2] %3",
+		ui->m_date->date().toString ("yyyy-MM-dd"),
+		ui->m_username->text(),
+		ui->m_comment->text());
+
+	LDCommentPtr comm (LDSpawn<LDComment> (commentText));
+
+	// Find a spot to place the new comment
+	for (obj = CurrentDocument()->getObject (0);
+		obj != null and obj->next() != null and not obj->next()->isScemantic();
+		obj = obj->next())
+	{
+		LDCommentPtr comm = obj.dynamicCast<LDComment>();
+
+		if (comm != null and comm->text().startsWith ("!HISTORY "))
+			ishistory = true;
+
+		if (prevIsHistory and not ishistory)
+		{
+			// Last line was history, this isn't, thus insert the new history
+			// line here.
+			break;
+		}
+
+		prevIsHistory = ishistory;
+	}
+
+	int idx = obj ? obj->lineNumber() : 0;
+	CurrentDocument()->insertObj (idx++, comm);
+
+	// If we're adding a history line right before a scemantic object, pad it
+	// an empty line
+	if (obj and obj->next() and obj->next()->isScemantic())
+		CurrentDocument()->insertObj (idx, LDEmptyPtr (LDSpawn<LDEmpty>()));
+
+	buildObjList();
+	delete ui;
+}
+
+void MainWindow::actionSplitLines()
+{
+	bool ok;
+	int segments = QInputDialog::getInt (g_win, APPNAME, "Amount of segments:", cfg::SplitLinesSegments, 0,
+		std::numeric_limits<int>::max(), 1, &ok);
+
+	if (not ok)
+		return;
+
+	cfg::SplitLinesSegments = segments;
+
+	for (LDObjectPtr obj : Selection())
+	{
+		if (not Eq (obj->type(), OBJ_Line, OBJ_CondLine))
+			continue;
+
+		QVector<LDObjectPtr> newsegs;
+
+		for (int i = 0; i < segments; ++i)
+		{
+			LDObjectPtr segment;
+			Vertex v0, v1;
+
+			v0.apply ([&](Axis ax, double& a)
+			{
+				double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
+				a = (obj->vertex (0)[ax] + ((len * i) / segments));
+			});
+
+			v1.apply ([&](Axis ax, double& a)
+			{
+				double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
+				a = (obj->vertex (0)[ax] + ((len * (i + 1)) / segments));
+			});
+
+			if (obj->type() == OBJ_Line)
+				segment = LDSpawn<LDLine> (v0, v1);
+			else
+				segment = LDSpawn<LDCondLine> (v0, v1, obj->vertex (2), obj->vertex (3));
+
+			newsegs << segment;
+		}
+
+		int ln = obj->lineNumber();
+
+		for (LDObjectPtr seg : newsegs)
+			CurrentDocument()->insertObj (ln++, seg);
+
+		obj->destroy();
+	}
+
+	buildObjList();
+	g_win->refresh();
+}
--- a/src/addObjectDialog.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,396 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QGridLayout>
-#include <QCheckBox>
-#include <QDialogButtonBox>
-#include <QSpinBox>
-#include <QLabel>
-#include <QListWidget>
-#include <QTreeWidget>
-#include <QLineEdit>
-#include <QPushButton>
-#include "mainWindow.h"
-#include "addObjectDialog.h"
-#include "ldDocument.h"
-#include "colors.h"
-#include "colorSelector.h"
-#include "editHistory.h"
-#include "radioGroup.h"
-#include "miscallenous.h"
-#include "primitives.h"
-
-// =============================================================================
-//
-AddObjectDialog::AddObjectDialog (const LDObjectType type, LDObjectPtr obj, QWidget* parent) :
-	QDialog (parent)
-{
-	setlocale (LC_ALL, "C");
-
-	int coordCount = 0;
-	QString typeName = LDObject::typeName (type);
-
-	switch (type)
-	{
-		case OBJ_Comment:
-		{
-			le_comment = new QLineEdit;
-
-			if (obj)
-				le_comment->setText (obj.staticCast<LDComment>()->text());
-
-			le_comment->setMinimumWidth (384);
-		} break;
-
-		case OBJ_Line:
-		{
-			coordCount = 6;
-		} break;
-
-		case OBJ_Triangle:
-		{
-			coordCount = 9;
-		} break;
-
-		case OBJ_Quad:
-		case OBJ_CondLine:
-		{
-			coordCount = 12;
-		} break;
-
-		case OBJ_BFC:
-		{
-			rb_bfcType = new RadioGroup ("Statement", {}, 0, Qt::Vertical);
-
-			for_enum (BFCStatement, i)
-			{
-				// Separate these in two columns
-				if (int (i) == int (BFCStatement::NumValues) / 2)
-					rb_bfcType->rowBreak();
-
-				rb_bfcType->addButton (LDBFC::StatementStrings[int (i)]);
-			}
-
-			if (obj)
-				rb_bfcType->setValue ((int) obj.staticCast<LDBFC>()->statement());
-		} break;
-
-		case OBJ_Subfile:
-		{
-			coordCount = 3;
-			tw_subfileList = new QTreeWidget();
-			tw_subfileList->setHeaderLabel (tr ("Primitives"));
-			PopulatePrimitives (tw_subfileList, (obj != null ? obj.staticCast<LDSubfile>()->fileInfo()->name() : ""));
-
-			connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged()));
-			lb_subfileName = new QLabel ("File:");
-			le_subfileName = new QLineEdit;
-			le_subfileName->setFocus();
-
-			if (obj)
-			{
-				LDSubfilePtr ref = obj.staticCast<LDSubfile>();
-				le_subfileName->setText (ref->fileInfo()->name());
-			}
-		} break;
-
-		default:
-		{
-			Critical (format ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName));
-		} return;
-	}
-
-	QPixmap icon = GetIcon (format ("add-%1", typeName));
-	LDObjectPtr 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)
-			m_color = obj->color();
-		else
-			m_color = (type == OBJ_CondLine or type == OBJ_Line) ? EdgeColor() : MainColor();
-
-		pb_color = new QPushButton;
-		setButtonBackground (pb_color, m_color);
-		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 OBJ_Line:
-		case OBJ_CondLine:
-		case OBJ_Triangle:
-		case OBJ_Quad:
-			// Apply coordinates
-			if (obj != null)
-			{
-				for (int i = 0; i < coordCount / 3; ++i)
-				{
-					obj->vertex (i).apply ([&](Axis ax, double value)
-					{
-						dsb_coords[(i * 3) + ax]->setValue (value);
-					});
-				}
-			}
-			break;
-
-		case OBJ_Comment:
-			layout->addWidget (le_comment, 0, 1);
-			break;
-
-		case OBJ_BFC:
-			layout->addWidget (rb_bfcType, 0, 1);
-			break;
-
-		case OBJ_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())
-	{
-		LDMatrixObjectPtr mo = obj.dynamicCast<LDMatrixObject>();
-		QLabel* lb_matrix = new QLabel ("Matrix:");
-		le_matrix = new QLineEdit;
-		// le_matrix->setValidator (new QDoubleValidator);
-		Matrix defaultMatrix = IdentityMatrix;
-
-		if (mo != null)
-		{
-			mo->position().apply ([&](Axis ax, double value)
-			{
-				dsb_coords[ax]->setValue (value);
-			});
-
-			defaultMatrix = mo->transform();
-		}
-
-		le_matrix->setText (defaultMatrix.toString());
-		layout->addWidget (lb_matrix, 4, 1);
-		layout->addWidget (le_matrix, 4, 2, 1, 3);
-	}
-
-	if (defaults->isColored())
-		layout->addWidget (pb_color, 1, 0);
-
-	if (coordCount > 0)
-	{
-		QGridLayout* const qCoordLayout = new QGridLayout;
-
-		for (int i = 0; i < coordCount; ++i)
-			qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3));
-
-		layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3);
-	}
-
-	QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-	QWidget::connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept()));
-	QWidget::connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject()));
-	layout->addWidget (bbx_buttons, 5, 0, 1, 4);
-	setLayout (layout);
-	setWindowTitle (format (tr ("Edit %1"), typeName));
-	setWindowIcon (icon);
-}
-
-// =============================================================================
-// =============================================================================
-void AddObjectDialog::setButtonBackground (QPushButton* button, LDColor color)
-{
-	button->setIcon (GetIcon ("palette"));
-	button->setAutoFillBackground (true);
-
-	if (color != null)
-		button->setStyleSheet (format ("background-color: %1", color.hexcode()));
-}
-
-// =============================================================================
-// =============================================================================
-QString AddObjectDialog::currentSubfileName()
-{
-	SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem());
-
-	if (item->primitive() == null)
-		return ""; // selected a heading
-
-	return item->primitive()->name;
-}
-
-// =============================================================================
-// =============================================================================
-void AddObjectDialog::slot_colorButtonClicked()
-{
-	ColorSelector::selectColor (m_color, m_color, this);
-	setButtonBackground (pb_color, m_color);
-}
-
-// =============================================================================
-// =============================================================================
-void AddObjectDialog::slot_subfileTypeChanged()
-{
-	QString name = currentSubfileName();
-
-	if (name.length() > 0)
-		le_subfileName->setText (name);
-}
-
-// =============================================================================
-// =============================================================================
-template<typename T>
-static QSharedPointer<T> InitObject (LDObjectPtr& obj)
-{
-	if (obj == null)
-		obj = LDSpawn<T>();
-
-	return obj.staticCast<T>();
-}
-
-// =============================================================================
-// =============================================================================
-void AddObjectDialog::staticDialog (const LDObjectType type, LDObjectPtr obj)
-{
-	setlocale (LC_ALL, "C");
-
-	// FIXME: Redirect to Edit Raw
-	if (obj and obj->type() == OBJ_Error)
-		return;
-
-	if (type == OBJ_Empty)
-		return; // Nothing to edit with empties
-
-	const bool newObject = (obj == null);
-	Matrix transform = IdentityMatrix;
-	AddObjectDialog dlg (type, obj);
-
-	assert (obj == null or obj->type() == type);
-
-	if (dlg.exec() == QDialog::Rejected)
-		return;
-
-	if (type == OBJ_Subfile)
-	{
-		QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts);
-
-		if (matrixstrvals.size() == 9)
-		{
-			double matrixvals[9];
-			int i = 0;
-
-			for (QString val : matrixstrvals)
-				matrixvals[i++] = val.toFloat();
-
-			transform = Matrix (matrixvals);
-		}
-	}
-
-	switch (type)
-	{
-		case OBJ_Comment:
-		{
-			LDCommentPtr comm = InitObject<LDComment> (obj);
-			comm->setText (dlg.le_comment->text());
-		}
-		break;
-
-		case OBJ_Line:
-		case OBJ_Triangle:
-		case OBJ_Quad:
-		case OBJ_CondLine:
-		{
-			if (not obj)
-				obj = LDObject::getDefault (type);
-
-			for (int i = 0; i < obj->numVertices(); ++i)
-			{
-				Vertex v;
-
-				v.apply ([&](Axis ax, double& value)
-				{
-					value = dlg.dsb_coords[(i * 3) + ax]->value();
-				});
-
-				obj->setVertex (i, v);
-			}
-		} break;
-
-		case OBJ_BFC:
-		{
-			LDBFCPtr bfc = InitObject<LDBFC> (obj);
-			assert (IsWithin (dlg.rb_bfcType->value(), 0, int (BFCStatement::NumValues) - 1));
-			bfc->setStatement (BFCStatement (dlg.rb_bfcType->value()));
-		} break;
-
-		case OBJ_Subfile:
-		{
-			QString name = dlg.le_subfileName->text();
-
-			if (name.length() == 0)
-				return; // no subfile filename
-
-			LDDocumentPtr file = GetDocument (name);
-
-			if (not file)
-			{
-				Critical (format ("Couldn't open `%1': %2", name, strerror (errno)));
-				return;
-			}
-
-			LDSubfilePtr ref = InitObject<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.m_color);
-
-	if (newObject)
-	{
-		int idx = g_win->getInsertionPoint();
-		CurrentDocument()->insertObj (idx, obj);
-	}
-
-	g_win->refresh();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/addObjectDialog.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,396 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QGridLayout>
+#include <QCheckBox>
+#include <QDialogButtonBox>
+#include <QSpinBox>
+#include <QLabel>
+#include <QListWidget>
+#include <QTreeWidget>
+#include <QLineEdit>
+#include <QPushButton>
+#include "mainWindow.h"
+#include "addObjectDialog.h"
+#include "ldDocument.h"
+#include "colors.h"
+#include "colorSelector.h"
+#include "editHistory.h"
+#include "radioGroup.h"
+#include "miscallenous.h"
+#include "primitives.h"
+
+// =============================================================================
+//
+AddObjectDialog::AddObjectDialog (const LDObjectType type, LDObjectPtr obj, QWidget* parent) :
+	QDialog (parent)
+{
+	setlocale (LC_ALL, "C");
+
+	int coordCount = 0;
+	QString typeName = LDObject::typeName (type);
+
+	switch (type)
+	{
+		case OBJ_Comment:
+		{
+			le_comment = new QLineEdit;
+
+			if (obj)
+				le_comment->setText (obj.staticCast<LDComment>()->text());
+
+			le_comment->setMinimumWidth (384);
+		} break;
+
+		case OBJ_Line:
+		{
+			coordCount = 6;
+		} break;
+
+		case OBJ_Triangle:
+		{
+			coordCount = 9;
+		} break;
+
+		case OBJ_Quad:
+		case OBJ_CondLine:
+		{
+			coordCount = 12;
+		} break;
+
+		case OBJ_BFC:
+		{
+			rb_bfcType = new RadioGroup ("Statement", {}, 0, Qt::Vertical);
+
+			for_enum (BFCStatement, i)
+			{
+				// Separate these in two columns
+				if (int (i) == int (BFCStatement::NumValues) / 2)
+					rb_bfcType->rowBreak();
+
+				rb_bfcType->addButton (LDBFC::StatementStrings[int (i)]);
+			}
+
+			if (obj)
+				rb_bfcType->setValue ((int) obj.staticCast<LDBFC>()->statement());
+		} break;
+
+		case OBJ_Subfile:
+		{
+			coordCount = 3;
+			tw_subfileList = new QTreeWidget();
+			tw_subfileList->setHeaderLabel (tr ("Primitives"));
+			PopulatePrimitives (tw_subfileList, (obj != null ? obj.staticCast<LDSubfile>()->fileInfo()->name() : ""));
+
+			connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged()));
+			lb_subfileName = new QLabel ("File:");
+			le_subfileName = new QLineEdit;
+			le_subfileName->setFocus();
+
+			if (obj)
+			{
+				LDSubfilePtr ref = obj.staticCast<LDSubfile>();
+				le_subfileName->setText (ref->fileInfo()->name());
+			}
+		} break;
+
+		default:
+		{
+			Critical (format ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName));
+		} return;
+	}
+
+	QPixmap icon = GetIcon (format ("add-%1", typeName));
+	LDObjectPtr 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)
+			m_color = obj->color();
+		else
+			m_color = (type == OBJ_CondLine or type == OBJ_Line) ? EdgeColor() : MainColor();
+
+		pb_color = new QPushButton;
+		setButtonBackground (pb_color, m_color);
+		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 OBJ_Line:
+		case OBJ_CondLine:
+		case OBJ_Triangle:
+		case OBJ_Quad:
+			// Apply coordinates
+			if (obj != null)
+			{
+				for (int i = 0; i < coordCount / 3; ++i)
+				{
+					obj->vertex (i).apply ([&](Axis ax, double value)
+					{
+						dsb_coords[(i * 3) + ax]->setValue (value);
+					});
+				}
+			}
+			break;
+
+		case OBJ_Comment:
+			layout->addWidget (le_comment, 0, 1);
+			break;
+
+		case OBJ_BFC:
+			layout->addWidget (rb_bfcType, 0, 1);
+			break;
+
+		case OBJ_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())
+	{
+		LDMatrixObjectPtr mo = obj.dynamicCast<LDMatrixObject>();
+		QLabel* lb_matrix = new QLabel ("Matrix:");
+		le_matrix = new QLineEdit;
+		// le_matrix->setValidator (new QDoubleValidator);
+		Matrix defaultMatrix = IdentityMatrix;
+
+		if (mo != null)
+		{
+			mo->position().apply ([&](Axis ax, double value)
+			{
+				dsb_coords[ax]->setValue (value);
+			});
+
+			defaultMatrix = mo->transform();
+		}
+
+		le_matrix->setText (defaultMatrix.toString());
+		layout->addWidget (lb_matrix, 4, 1);
+		layout->addWidget (le_matrix, 4, 2, 1, 3);
+	}
+
+	if (defaults->isColored())
+		layout->addWidget (pb_color, 1, 0);
+
+	if (coordCount > 0)
+	{
+		QGridLayout* const qCoordLayout = new QGridLayout;
+
+		for (int i = 0; i < coordCount; ++i)
+			qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3));
+
+		layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3);
+	}
+
+	QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+	QWidget::connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept()));
+	QWidget::connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject()));
+	layout->addWidget (bbx_buttons, 5, 0, 1, 4);
+	setLayout (layout);
+	setWindowTitle (format (tr ("Edit %1"), typeName));
+	setWindowIcon (icon);
+}
+
+// =============================================================================
+// =============================================================================
+void AddObjectDialog::setButtonBackground (QPushButton* button, LDColor color)
+{
+	button->setIcon (GetIcon ("palette"));
+	button->setAutoFillBackground (true);
+
+	if (color != null)
+		button->setStyleSheet (format ("background-color: %1", color.hexcode()));
+}
+
+// =============================================================================
+// =============================================================================
+QString AddObjectDialog::currentSubfileName()
+{
+	SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem());
+
+	if (item->primitive() == null)
+		return ""; // selected a heading
+
+	return item->primitive()->name;
+}
+
+// =============================================================================
+// =============================================================================
+void AddObjectDialog::slot_colorButtonClicked()
+{
+	ColorSelector::selectColor (m_color, m_color, this);
+	setButtonBackground (pb_color, m_color);
+}
+
+// =============================================================================
+// =============================================================================
+void AddObjectDialog::slot_subfileTypeChanged()
+{
+	QString name = currentSubfileName();
+
+	if (name.length() > 0)
+		le_subfileName->setText (name);
+}
+
+// =============================================================================
+// =============================================================================
+template<typename T>
+static QSharedPointer<T> InitObject (LDObjectPtr& obj)
+{
+	if (obj == null)
+		obj = LDSpawn<T>();
+
+	return obj.staticCast<T>();
+}
+
+// =============================================================================
+// =============================================================================
+void AddObjectDialog::staticDialog (const LDObjectType type, LDObjectPtr obj)
+{
+	setlocale (LC_ALL, "C");
+
+	// FIXME: Redirect to Edit Raw
+	if (obj and obj->type() == OBJ_Error)
+		return;
+
+	if (type == OBJ_Empty)
+		return; // Nothing to edit with empties
+
+	const bool newObject = (obj == null);
+	Matrix transform = IdentityMatrix;
+	AddObjectDialog dlg (type, obj);
+
+	assert (obj == null or obj->type() == type);
+
+	if (dlg.exec() == QDialog::Rejected)
+		return;
+
+	if (type == OBJ_Subfile)
+	{
+		QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts);
+
+		if (matrixstrvals.size() == 9)
+		{
+			double matrixvals[9];
+			int i = 0;
+
+			for (QString val : matrixstrvals)
+				matrixvals[i++] = val.toFloat();
+
+			transform = Matrix (matrixvals);
+		}
+	}
+
+	switch (type)
+	{
+		case OBJ_Comment:
+		{
+			LDCommentPtr comm = InitObject<LDComment> (obj);
+			comm->setText (dlg.le_comment->text());
+		}
+		break;
+
+		case OBJ_Line:
+		case OBJ_Triangle:
+		case OBJ_Quad:
+		case OBJ_CondLine:
+		{
+			if (not obj)
+				obj = LDObject::getDefault (type);
+
+			for (int i = 0; i < obj->numVertices(); ++i)
+			{
+				Vertex v;
+
+				v.apply ([&](Axis ax, double& value)
+				{
+					value = dlg.dsb_coords[(i * 3) + ax]->value();
+				});
+
+				obj->setVertex (i, v);
+			}
+		} break;
+
+		case OBJ_BFC:
+		{
+			LDBFCPtr bfc = InitObject<LDBFC> (obj);
+			assert (IsWithin (dlg.rb_bfcType->value(), 0, int (BFCStatement::NumValues) - 1));
+			bfc->setStatement (BFCStatement (dlg.rb_bfcType->value()));
+		} break;
+
+		case OBJ_Subfile:
+		{
+			QString name = dlg.le_subfileName->text();
+
+			if (name.length() == 0)
+				return; // no subfile filename
+
+			LDDocumentPtr file = GetDocument (name);
+
+			if (not file)
+			{
+				Critical (format ("Couldn't open `%1': %2", name, strerror (errno)));
+				return;
+			}
+
+			LDSubfilePtr ref = InitObject<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.m_color);
+
+	if (newObject)
+	{
+		int idx = g_win->getInsertionPoint();
+		CurrentDocument()->insertObj (idx, obj);
+	}
+
+	g_win->refresh();
+}
--- a/src/basics.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,336 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QObject>
-#include <QStringList>
-#include <QTextStream>
-#include <QFile>
-#include <assert.h>
-#include "main.h"
-#include "basics.h"
-#include "miscallenous.h"
-#include "ldObject.h"
-#include "ldDocument.h"
-
-Vertex::Vertex() :
-	QVector3D() {}
-
-Vertex::Vertex (const QVector3D& a) :
-	QVector3D (a) {}
-
-Vertex::Vertex (qreal xpos, qreal ypos, qreal zpos) :
-	QVector3D(xpos, ypos, zpos) {}
-
-
-void Vertex::transform (const Matrix& matr, const Vertex& pos)
-{
-	double x2 = (matr[0] * x()) + (matr[1] * y()) + (matr[2] * z()) + pos.x();
-	double y2 = (matr[3] * x()) + (matr[4] * y()) + (matr[5] * z()) + pos.y();
-	double z2 = (matr[6] * x()) + (matr[7] * y()) + (matr[8] * z()) + pos.z();
-	setX (x2);
-	setY (y2);
-	setZ (z2);
-}
-
-void Vertex::apply (ApplyFunction func)
-{
-	double newX = x(), newY = y(), newZ = z();
-	func (X, newX);
-	func (Y, newY);
-	func (Z, newZ);
-	*this = Vertex (newX, newY, newZ);
-}
-
-void Vertex::apply (ApplyConstFunction func) const
-{
-	func (X, x());
-	func (Y, y());
-	func (Z, z());
-}
-
-double Vertex::operator[] (Axis ax) const
-{
-	switch (ax)
-	{
-		case X: return x();
-		case Y: return y();
-		case Z: return z();
-	}
-
-	assert (false);
-	return 0.0;
-}
-
-void Vertex::setCoordinate (Axis ax, qreal value)
-{
-	switch (ax)
-	{
-		case X: setX (value); break;
-		case Y: setY (value); break;
-		case Z: setZ (value); break;
-	}
-}
-
-QString Vertex::toString (bool mangled) const
-{
-	if (mangled)
-		return format ("(%1, %2, %3)", x(), y(), z());
-
-	return format ("%1 %2 %3", x(), y(), z());
-}
-
-bool Vertex::operator< (const Vertex& other) const
-{
-	if (x() != other.x()) return x() < other.x();
-	if (y() != other.y()) return y() < other.y();
-	if (z() != other.z()) return z() < other.z();
-	return false;
-}
-
-// =============================================================================
-//
-Matrix::Matrix (double vals[])
-{
-	for (int i = 0; i < 9; ++i)
-		m_vals[i] = vals[i];
-}
-
-// =============================================================================
-//
-Matrix::Matrix (double fillval)
-{
-	for (int i = 0; i < 9; ++i)
-		m_vals[i] = fillval;
-}
-
-// =============================================================================
-//
-Matrix::Matrix (const std::initializer_list<double>& vals)
-{
-	assert (vals.size() == 9);
-	memcpy (&m_vals[0], vals.begin(), sizeof m_vals);
-}
-
-// =============================================================================
-//
-void Matrix::dump() const
-{
-	for (int i = 0; i < 3; ++i)
-	{
-		for (int j = 0; j < 3; ++j)
-			print ("%1\t", m_vals[i * 3 + j]);
-
-		print ("\n");
-	}
-}
-
-// =============================================================================
-//
-QString Matrix::toString() const
-{
-	QString val;
-
-	for (int i = 0; i < 9; ++i)
-	{
-		if (i > 0)
-			val += ' ';
-
-		val += QString::number (m_vals[i]);
-	}
-
-	return val;
-}
-
-// =============================================================================
-//
-void Matrix::zero()
-{
-	memset (&m_vals[0], 0, sizeof m_vals);
-}
-
-// =============================================================================
-//
-Matrix Matrix::mult (const Matrix& other) const
-{
-	Matrix val;
-	val.zero();
-
-	for (int i = 0; i < 3; ++i)
-	for (int j = 0; j < 3; ++j)
-	for (int k = 0; k < 3; ++k)
-		val[(i * 3) + j] += m_vals[(i * 3) + k] * other[(k * 3) + j];
-
-	return val;
-}
-
-// =============================================================================
-//
-Matrix& Matrix::operator= (const Matrix& other)
-{
-	memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals);
-	return *this;
-}
-
-// =============================================================================
-//
-double Matrix::getDeterminant() const
-{
-	return (value (0) * value (4) * value (8)) +
-		   (value (1) * value (5) * value (6)) +
-		   (value (2) * value (3) * value (7)) -
-		   (value (2) * value (4) * value (6)) -
-		   (value (1) * value (3) * value (8)) -
-		   (value (0) * value (5) * value (7));
-}
-
-// =============================================================================
-//
-bool Matrix::operator== (const Matrix& other) const
-{
-	for (int i = 0; i < 9; ++i)
-	{
-		if (value (i) != other[i])
-			return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-LDBoundingBox::LDBoundingBox()
-{
-	reset();
-}
-
-// =============================================================================
-//
-void LDBoundingBox::calculateFromCurrentDocument()
-{
-	reset();
-
-	if (CurrentDocument() == null)
-		return;
-
-	for (LDObjectPtr obj : CurrentDocument()->objects())
-		calcObject (obj);
-}
-
-// =============================================================================
-//
-void LDBoundingBox::calcObject (LDObjectPtr obj)
-{
-	switch (obj->type())
-	{
-		case OBJ_Line:
-		case OBJ_Triangle:
-		case OBJ_Quad:
-		case OBJ_CondLine:
-		{
-			for (int i = 0; i < obj->numVertices(); ++i)
-				calcVertex (obj->vertex (i));
-		} break;
-
-		case OBJ_Subfile:
-		{
-			LDSubfilePtr ref = obj.staticCast<LDSubfile>();
-			LDObjectList objs = ref->inlineContents (true, false);
-
-			for (LDObjectPtr obj : objs)
-			{
-				calcObject (obj);
-				obj->destroy();
-			}
-		}
-		break;
-
-		default:
-			break;
-	}
-}
-
-// =============================================================================
-//
-LDBoundingBox& LDBoundingBox::operator<< (const Vertex& v)
-{
-	calcVertex (v);
-	return *this;
-}
-
-// =============================================================================
-//
-LDBoundingBox& LDBoundingBox::operator<< (LDObjectPtr obj)
-{
-	calcObject (obj);
-	return *this;
-}
-
-// =============================================================================
-//
-void LDBoundingBox::calcVertex (const Vertex& vertex)
-{
-	m_vertex0.setX (Min (vertex.x(), m_vertex0.x()));
-	m_vertex0.setY (Min (vertex.y(), m_vertex0.y()));
-	m_vertex0.setZ (Min (vertex.z(), m_vertex0.z()));
-	m_vertex1.setX (Max (vertex.x(), m_vertex1.x()));
-	m_vertex1.setY (Max (vertex.y(), m_vertex1.y()));
-	m_vertex1.setZ (Max (vertex.z(), m_vertex1.z()));
-	setEmpty (false);
-}
-
-// =============================================================================
-//
-void LDBoundingBox::reset()
-{
-	m_vertex0 = Vertex (10000.0, 10000.0, 10000.0);
-	m_vertex1 = Vertex (-10000.0, -10000.0, -10000.0);
-	setEmpty (true);
-}
-
-// =============================================================================
-//
-double LDBoundingBox::longestMeasurement() const
-{
-	double xscale = (m_vertex0.x() - m_vertex1.x());
-	double yscale = (m_vertex0.y() - m_vertex1.y());
-	double zscale = (m_vertex0.z() - m_vertex1.z());
-	double size = zscale;
-
-	if (xscale > yscale)
-	{
-		if (xscale > zscale)
-			size = xscale;
-	}
-	elif (yscale > zscale)
-		size = yscale;
-
-	if (Abs (size) >= 2.0)
-		return Abs (size / 2);
-
-	return 1.0;
-}
-
-// =============================================================================
-//
-Vertex LDBoundingBox::center() const
-{
-	return Vertex (
-		(m_vertex0.x() + m_vertex1.x()) / 2,
-		(m_vertex0.y() + m_vertex1.y()) / 2,
-		(m_vertex0.z() + m_vertex1.z()) / 2);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/basics.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,336 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QObject>
+#include <QStringList>
+#include <QTextStream>
+#include <QFile>
+#include <assert.h>
+#include "main.h"
+#include "basics.h"
+#include "miscallenous.h"
+#include "ldObject.h"
+#include "ldDocument.h"
+
+Vertex::Vertex() :
+	QVector3D() {}
+
+Vertex::Vertex (const QVector3D& a) :
+	QVector3D (a) {}
+
+Vertex::Vertex (qreal xpos, qreal ypos, qreal zpos) :
+	QVector3D(xpos, ypos, zpos) {}
+
+
+void Vertex::transform (const Matrix& matr, const Vertex& pos)
+{
+	double x2 = (matr[0] * x()) + (matr[1] * y()) + (matr[2] * z()) + pos.x();
+	double y2 = (matr[3] * x()) + (matr[4] * y()) + (matr[5] * z()) + pos.y();
+	double z2 = (matr[6] * x()) + (matr[7] * y()) + (matr[8] * z()) + pos.z();
+	setX (x2);
+	setY (y2);
+	setZ (z2);
+}
+
+void Vertex::apply (ApplyFunction func)
+{
+	double newX = x(), newY = y(), newZ = z();
+	func (X, newX);
+	func (Y, newY);
+	func (Z, newZ);
+	*this = Vertex (newX, newY, newZ);
+}
+
+void Vertex::apply (ApplyConstFunction func) const
+{
+	func (X, x());
+	func (Y, y());
+	func (Z, z());
+}
+
+double Vertex::operator[] (Axis ax) const
+{
+	switch (ax)
+	{
+		case X: return x();
+		case Y: return y();
+		case Z: return z();
+	}
+
+	assert (false);
+	return 0.0;
+}
+
+void Vertex::setCoordinate (Axis ax, qreal value)
+{
+	switch (ax)
+	{
+		case X: setX (value); break;
+		case Y: setY (value); break;
+		case Z: setZ (value); break;
+	}
+}
+
+QString Vertex::toString (bool mangled) const
+{
+	if (mangled)
+		return format ("(%1, %2, %3)", x(), y(), z());
+
+	return format ("%1 %2 %3", x(), y(), z());
+}
+
+bool Vertex::operator< (const Vertex& other) const
+{
+	if (x() != other.x()) return x() < other.x();
+	if (y() != other.y()) return y() < other.y();
+	if (z() != other.z()) return z() < other.z();
+	return false;
+}
+
+// =============================================================================
+//
+Matrix::Matrix (double vals[])
+{
+	for (int i = 0; i < 9; ++i)
+		m_vals[i] = vals[i];
+}
+
+// =============================================================================
+//
+Matrix::Matrix (double fillval)
+{
+	for (int i = 0; i < 9; ++i)
+		m_vals[i] = fillval;
+}
+
+// =============================================================================
+//
+Matrix::Matrix (const std::initializer_list<double>& vals)
+{
+	assert (vals.size() == 9);
+	memcpy (&m_vals[0], vals.begin(), sizeof m_vals);
+}
+
+// =============================================================================
+//
+void Matrix::dump() const
+{
+	for (int i = 0; i < 3; ++i)
+	{
+		for (int j = 0; j < 3; ++j)
+			print ("%1\t", m_vals[i * 3 + j]);
+
+		print ("\n");
+	}
+}
+
+// =============================================================================
+//
+QString Matrix::toString() const
+{
+	QString val;
+
+	for (int i = 0; i < 9; ++i)
+	{
+		if (i > 0)
+			val += ' ';
+
+		val += QString::number (m_vals[i]);
+	}
+
+	return val;
+}
+
+// =============================================================================
+//
+void Matrix::zero()
+{
+	memset (&m_vals[0], 0, sizeof m_vals);
+}
+
+// =============================================================================
+//
+Matrix Matrix::mult (const Matrix& other) const
+{
+	Matrix val;
+	val.zero();
+
+	for (int i = 0; i < 3; ++i)
+	for (int j = 0; j < 3; ++j)
+	for (int k = 0; k < 3; ++k)
+		val[(i * 3) + j] += m_vals[(i * 3) + k] * other[(k * 3) + j];
+
+	return val;
+}
+
+// =============================================================================
+//
+Matrix& Matrix::operator= (const Matrix& other)
+{
+	memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals);
+	return *this;
+}
+
+// =============================================================================
+//
+double Matrix::getDeterminant() const
+{
+	return (value (0) * value (4) * value (8)) +
+		   (value (1) * value (5) * value (6)) +
+		   (value (2) * value (3) * value (7)) -
+		   (value (2) * value (4) * value (6)) -
+		   (value (1) * value (3) * value (8)) -
+		   (value (0) * value (5) * value (7));
+}
+
+// =============================================================================
+//
+bool Matrix::operator== (const Matrix& other) const
+{
+	for (int i = 0; i < 9; ++i)
+	{
+		if (value (i) != other[i])
+			return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+LDBoundingBox::LDBoundingBox()
+{
+	reset();
+}
+
+// =============================================================================
+//
+void LDBoundingBox::calculateFromCurrentDocument()
+{
+	reset();
+
+	if (CurrentDocument() == null)
+		return;
+
+	for (LDObjectPtr obj : CurrentDocument()->objects())
+		calcObject (obj);
+}
+
+// =============================================================================
+//
+void LDBoundingBox::calcObject (LDObjectPtr obj)
+{
+	switch (obj->type())
+	{
+		case OBJ_Line:
+		case OBJ_Triangle:
+		case OBJ_Quad:
+		case OBJ_CondLine:
+		{
+			for (int i = 0; i < obj->numVertices(); ++i)
+				calcVertex (obj->vertex (i));
+		} break;
+
+		case OBJ_Subfile:
+		{
+			LDSubfilePtr ref = obj.staticCast<LDSubfile>();
+			LDObjectList objs = ref->inlineContents (true, false);
+
+			for (LDObjectPtr obj : objs)
+			{
+				calcObject (obj);
+				obj->destroy();
+			}
+		}
+		break;
+
+		default:
+			break;
+	}
+}
+
+// =============================================================================
+//
+LDBoundingBox& LDBoundingBox::operator<< (const Vertex& v)
+{
+	calcVertex (v);
+	return *this;
+}
+
+// =============================================================================
+//
+LDBoundingBox& LDBoundingBox::operator<< (LDObjectPtr obj)
+{
+	calcObject (obj);
+	return *this;
+}
+
+// =============================================================================
+//
+void LDBoundingBox::calcVertex (const Vertex& vertex)
+{
+	m_vertex0.setX (Min (vertex.x(), m_vertex0.x()));
+	m_vertex0.setY (Min (vertex.y(), m_vertex0.y()));
+	m_vertex0.setZ (Min (vertex.z(), m_vertex0.z()));
+	m_vertex1.setX (Max (vertex.x(), m_vertex1.x()));
+	m_vertex1.setY (Max (vertex.y(), m_vertex1.y()));
+	m_vertex1.setZ (Max (vertex.z(), m_vertex1.z()));
+	setEmpty (false);
+}
+
+// =============================================================================
+//
+void LDBoundingBox::reset()
+{
+	m_vertex0 = Vertex (10000.0, 10000.0, 10000.0);
+	m_vertex1 = Vertex (-10000.0, -10000.0, -10000.0);
+	setEmpty (true);
+}
+
+// =============================================================================
+//
+double LDBoundingBox::longestMeasurement() const
+{
+	double xscale = (m_vertex0.x() - m_vertex1.x());
+	double yscale = (m_vertex0.y() - m_vertex1.y());
+	double zscale = (m_vertex0.z() - m_vertex1.z());
+	double size = zscale;
+
+	if (xscale > yscale)
+	{
+		if (xscale > zscale)
+			size = xscale;
+	}
+	elif (yscale > zscale)
+		size = yscale;
+
+	if (Abs (size) >= 2.0)
+		return Abs (size / 2);
+
+	return 1.0;
+}
+
+// =============================================================================
+//
+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/colorSelector.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,215 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  colorSelectDialog.cxx: Color selector box.
- */
-
-#include <QGraphicsScene>
-#include <QGraphicsItem>
-#include <QMouseEvent>
-#include <QScrollBar>
-#include <QColorDialog>
-#include "main.h"
-#include "mainWindow.h"
-#include "colorSelector.h"
-#include "colors.h"
-#include "configuration.h"
-#include "miscallenous.h"
-#include "ui_colorsel.h"
-
-static const int g_numColumns = 16;
-
-EXTERN_CFGENTRY (String, MainColor)
-EXTERN_CFGENTRY (Float, MainColorAlpha)
-
-// =============================================================================
-//
-ColorSelector::ColorSelector (LDColor defaultvalue, QWidget* parent) :
-	QDialog (parent)
-{
-	m_firstResize = true;
-	ui = new Ui_ColorSelUI;
-	ui->setupUi (this);
-	setSelection (defaultvalue);
-
-	QGridLayout* layout = new QGridLayout (this);
-
-	// Spawn color selector buttons
-	for (int i = 0; i < CountLDConfigColors(); ++i)
-	{
-		LDColor ldcolor = LDColor::fromIndex (i);
-		QPushButton* button = new QPushButton (this);
-		button->setMinimumSize (QSize (32, 32));
-		button->setMaximumSize (button->minimumSize());
-
-		if (ldcolor != null)
-		{
-			QString colorname;
-			QColor color (ldcolor.faceColor());
-
-			if (i == MainColorIndex)
-			{
-				color = QColor (cfg::MainColor);
-				color.setAlphaF (cfg::MainColorAlpha);
-			}
-
-			QString color2name (Luma (color) < 80 ? "white" : "black");
-			button->setAutoFillBackground (true);
-			button->setStyleSheet (format ("background-color: rgba(%1, %2, %3, %4); color: %5",
-				color.red(), color.green(), color.blue(), color.alpha(), color2name));
-			button->setCheckable (true);
-			button->setText (QString::number (ldcolor.index()));
-			button->setToolTip (format ("%1: %2", ldcolor.index(), ldcolor.name()));
-			m_buttons[i] = button;
-			m_buttonsReversed[button] = i;
-			connect (button, SIGNAL (clicked(bool)), this, SLOT (colorButtonClicked()));
-
-			if (ldcolor == selection())
-				button->setChecked (true);
-		}
-		else
-		{
-			button->setEnabled (false);
-		}
-
-		layout->addWidget (button, i / g_numColumns, i % g_numColumns);
-	}
-
-	QWidget* widget = new QWidget();
-	widget->setLayout (layout);
-	ui->definedColors->setWidget (widget);
-	connect (ui->directColor, SIGNAL (clicked (bool)), this, SLOT (chooseDirectColor()));
-
-	ui->definedColors->setMinimumWidth (ui->definedColors->widget()->width() + 16);
-
-#ifdef TRANSPARENT_DIRECT_COLORS
-	connect (ui->transparentDirectColor, SIGNAL (clicked (bool)), this, SLOT (transparentCheckboxClicked()));
-#else
-	ui->transparentDirectColor->hide();
-#endif
-
-	drawColorInfo();
-}
-
-// =============================================================================
-//
-ColorSelector::~ColorSelector()
-{
-	delete ui;
-}
-
-// =============================================================================
-//
-void ColorSelector::colorButtonClicked()
-{
-	QPushButton* button = qobject_cast<QPushButton*> (sender());
-	auto it = m_buttonsReversed.find (button);
-	LDColor color;
-
-	if (Q_UNLIKELY (button == null or it == m_buttonsReversed.end()
-		or (color = LDColor::fromIndex (*it)) == null))
-	{
-		print ("colorButtonClicked() called with invalid sender");
-		return;
-	}
-
-	if (selection() != null)
-	{
-		auto it2 = m_buttons.find (selection().index());
-
-		if (it2 != m_buttons.end())
-			(*it2)->setChecked (false);
-	}
-
-	setSelection (color);
-	button->setChecked (true);
-	drawColorInfo();
-}
-
-// =============================================================================
-//
-void ColorSelector::drawColorInfo()
-{
-	if (selection() == null)
-	{
-		ui->colorLabel->setText ("---");
-		ui->iconLabel->setPixmap (QPixmap());
-		ui->transparentDirectColor->setChecked (false);
-		return;
-	}
-
-	ui->colorLabel->setText (format ("%1 - %2", selection().indexString(),
-		(selection().isDirect() ? "<direct color>" : selection().name())));
-	ui->iconLabel->setPixmap (MakeColorIcon (selection(), 16).pixmap (16, 16));
-
-#ifdef TRANSPARENT_DIRECT_COLORS
-	ui->transparentDirectColor->setEnabled (selection().isDirect());
-	ui->transparentDirectColor->setChecked (selection().isDirect() and selection().faceColor().alphaF() < 1.0);
-#else
-	ui->transparentDirectColor->setChecked (false);
-	ui->transparentDirectColor->setEnabled (false);
-#endif
-}
-
-// =============================================================================
-//
-void ColorSelector::selectDirectColor (QColor col)
-{
-	int32 idx = (ui->transparentDirectColor->isChecked() ? 0x03000000 : 0x02000000);
-	idx |= (col.red() << 16) | (col.green() << 8) | (col.blue());
-	setSelection (LDColor::fromIndex (idx));
-	drawColorInfo();
-}
-
-// =============================================================================
-//
-void ColorSelector::chooseDirectColor()
-{
-	QColor defcolor = selection() != null ? selection().faceColor() : Qt::white;
-	QColor newcolor = QColorDialog::getColor (defcolor);
-
-	if (not newcolor.isValid())
-		return; // canceled
-
-	selectDirectColor (newcolor);
-}
-
-// =============================================================================
-//
-void ColorSelector::transparentCheckboxClicked()
-{
-	if (selection() == null or not selection().isDirect())
-		return;
-
-	selectDirectColor (selection().faceColor());
-}
-
-// =============================================================================
-//
-bool ColorSelector::selectColor (LDColor& val, LDColor defval, QWidget* parent)
-{
-	ColorSelector dlg (defval, parent);
-
-	if (dlg.exec() and dlg.selection() != null)
-	{
-		val = dlg.selection();
-		return true;
-	}
-
-	return false;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colorSelector.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,215 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  colorSelectDialog.cxx: Color selector box.
+ */
+
+#include <QGraphicsScene>
+#include <QGraphicsItem>
+#include <QMouseEvent>
+#include <QScrollBar>
+#include <QColorDialog>
+#include "main.h"
+#include "mainWindow.h"
+#include "colorSelector.h"
+#include "colors.h"
+#include "configuration.h"
+#include "miscallenous.h"
+#include "ui_colorsel.h"
+
+static const int g_numColumns = 16;
+
+EXTERN_CFGENTRY (String, MainColor)
+EXTERN_CFGENTRY (Float, MainColorAlpha)
+
+// =============================================================================
+//
+ColorSelector::ColorSelector (LDColor defaultvalue, QWidget* parent) :
+	QDialog (parent)
+{
+	m_firstResize = true;
+	ui = new Ui_ColorSelUI;
+	ui->setupUi (this);
+	setSelection (defaultvalue);
+
+	QGridLayout* layout = new QGridLayout (this);
+
+	// Spawn color selector buttons
+	for (int i = 0; i < CountLDConfigColors(); ++i)
+	{
+		LDColor ldcolor = LDColor::fromIndex (i);
+		QPushButton* button = new QPushButton (this);
+		button->setMinimumSize (QSize (32, 32));
+		button->setMaximumSize (button->minimumSize());
+
+		if (ldcolor != null)
+		{
+			QString colorname;
+			QColor color (ldcolor.faceColor());
+
+			if (i == MainColorIndex)
+			{
+				color = QColor (cfg::MainColor);
+				color.setAlphaF (cfg::MainColorAlpha);
+			}
+
+			QString color2name (Luma (color) < 80 ? "white" : "black");
+			button->setAutoFillBackground (true);
+			button->setStyleSheet (format ("background-color: rgba(%1, %2, %3, %4); color: %5",
+				color.red(), color.green(), color.blue(), color.alpha(), color2name));
+			button->setCheckable (true);
+			button->setText (QString::number (ldcolor.index()));
+			button->setToolTip (format ("%1: %2", ldcolor.index(), ldcolor.name()));
+			m_buttons[i] = button;
+			m_buttonsReversed[button] = i;
+			connect (button, SIGNAL (clicked(bool)), this, SLOT (colorButtonClicked()));
+
+			if (ldcolor == selection())
+				button->setChecked (true);
+		}
+		else
+		{
+			button->setEnabled (false);
+		}
+
+		layout->addWidget (button, i / g_numColumns, i % g_numColumns);
+	}
+
+	QWidget* widget = new QWidget();
+	widget->setLayout (layout);
+	ui->definedColors->setWidget (widget);
+	connect (ui->directColor, SIGNAL (clicked (bool)), this, SLOT (chooseDirectColor()));
+
+	ui->definedColors->setMinimumWidth (ui->definedColors->widget()->width() + 16);
+
+#ifdef TRANSPARENT_DIRECT_COLORS
+	connect (ui->transparentDirectColor, SIGNAL (clicked (bool)), this, SLOT (transparentCheckboxClicked()));
+#else
+	ui->transparentDirectColor->hide();
+#endif
+
+	drawColorInfo();
+}
+
+// =============================================================================
+//
+ColorSelector::~ColorSelector()
+{
+	delete ui;
+}
+
+// =============================================================================
+//
+void ColorSelector::colorButtonClicked()
+{
+	QPushButton* button = qobject_cast<QPushButton*> (sender());
+	auto it = m_buttonsReversed.find (button);
+	LDColor color;
+
+	if (Q_UNLIKELY (button == null or it == m_buttonsReversed.end()
+		or (color = LDColor::fromIndex (*it)) == null))
+	{
+		print ("colorButtonClicked() called with invalid sender");
+		return;
+	}
+
+	if (selection() != null)
+	{
+		auto it2 = m_buttons.find (selection().index());
+
+		if (it2 != m_buttons.end())
+			(*it2)->setChecked (false);
+	}
+
+	setSelection (color);
+	button->setChecked (true);
+	drawColorInfo();
+}
+
+// =============================================================================
+//
+void ColorSelector::drawColorInfo()
+{
+	if (selection() == null)
+	{
+		ui->colorLabel->setText ("---");
+		ui->iconLabel->setPixmap (QPixmap());
+		ui->transparentDirectColor->setChecked (false);
+		return;
+	}
+
+	ui->colorLabel->setText (format ("%1 - %2", selection().indexString(),
+		(selection().isDirect() ? "<direct color>" : selection().name())));
+	ui->iconLabel->setPixmap (MakeColorIcon (selection(), 16).pixmap (16, 16));
+
+#ifdef TRANSPARENT_DIRECT_COLORS
+	ui->transparentDirectColor->setEnabled (selection().isDirect());
+	ui->transparentDirectColor->setChecked (selection().isDirect() and selection().faceColor().alphaF() < 1.0);
+#else
+	ui->transparentDirectColor->setChecked (false);
+	ui->transparentDirectColor->setEnabled (false);
+#endif
+}
+
+// =============================================================================
+//
+void ColorSelector::selectDirectColor (QColor col)
+{
+	int32 idx = (ui->transparentDirectColor->isChecked() ? 0x03000000 : 0x02000000);
+	idx |= (col.red() << 16) | (col.green() << 8) | (col.blue());
+	setSelection (LDColor::fromIndex (idx));
+	drawColorInfo();
+}
+
+// =============================================================================
+//
+void ColorSelector::chooseDirectColor()
+{
+	QColor defcolor = selection() != null ? selection().faceColor() : Qt::white;
+	QColor newcolor = QColorDialog::getColor (defcolor);
+
+	if (not newcolor.isValid())
+		return; // canceled
+
+	selectDirectColor (newcolor);
+}
+
+// =============================================================================
+//
+void ColorSelector::transparentCheckboxClicked()
+{
+	if (selection() == null or not selection().isDirect())
+		return;
+
+	selectDirectColor (selection().faceColor());
+}
+
+// =============================================================================
+//
+bool ColorSelector::selectColor (LDColor& val, LDColor defval, QWidget* parent)
+{
+	ColorSelector dlg (defval, parent);
+
+	if (dlg.exec() and dlg.selection() != null)
+	{
+		val = dlg.selection();
+		return true;
+	}
+
+	return false;
+}
--- a/src/colors.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu 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 "colors.h"
-#include "ldDocument.h"
-#include "miscallenous.h"
-#include "mainWindow.h"
-#include "ldConfig.h"
-#include <QColor>
-
-static LDColor g_LDConfigColors[512];
-
-void InitColors()
-{
-	LDColorData* col;
-	print ("Initializing color information.\n");
-
-	// Always make sure there's 16 and 24 available. They're special like that.
-	col = new LDColorData;
-	col->m_faceColor =
-	col->m_hexcode = "#AAAAAA";
-	col->m_edgeColor = Qt::black;
-	g_LDConfigColors[16] = col;
-
-	col = new LDColorData;
-	col->m_faceColor =
-	col->m_edgeColor =
-	col->m_hexcode = "#000000";
-	g_LDConfigColors[24] = col;
-
-	LDConfigParser::parseLDConfig();
-}
-
-LDColor MainColor()
-{
-	return g_LDConfigColors[MainColorIndex];
-}
-
-LDColor EdgeColor()
-{
-	return g_LDConfigColors[EdgeColorIndex];
-}
-
-void LDColor::addLDConfigColor (qint32 index, LDColor color)
-{
-	assert (index >= 0 and index < countof (g_LDConfigColors));
-	g_LDConfigColors[index] = color;
-}
-
-LDColor LDColor::fromIndex (qint32 index)
-{
-	if (index < countof (g_LDConfigColors) and g_LDConfigColors[index] != null)
-		return g_LDConfigColors[index];
-
-	if (index >= 0x2000000)
-	{
-		// Direct color
-		QColor col;
-		col.setRed ((index & 0x0FF0000) >> 16);
-		col.setGreen ((index & 0x000FF00) >> 8);
-		col.setBlue (index & 0x00000FF);
-
-		if (index >= 0x3000000)
-			col.setAlpha (128);
-
-		LDColorData* color = new LDColorData;
-		color->m_name = "0x" + QString::number (index, 16).toUpper();
-		color->m_faceColor = col;
-		color->m_edgeColor = Luma (col) < 48 ? Qt::white : Qt::black;
-		color->m_hexcode = col.name();
-		color->m_index = index;
-		return LDColor (color);
-	}
-
-	return null;
-}
-
-QString LDColor::indexString() const
-{
-	if (isDirect())
-		return "0x" + QString::number (index(), 16).toUpper();
-
-	return QString::number (index());
-}
-
-bool LDColor::isDirect() const
-{
-	return index() >= 0x02000000;
-}
-
-bool LDColor::operator== (LDColor const& other)
-{
-	if ((data() == nullptr) ^ (other == nullptr))
-		return false;
-
-	if (data() != nullptr)
-		return index() == other.index();
-
-	// both are null
-	return true;
-}
-
-int Luma (const QColor& col)
-{
-	return (0.2126f * col.red()) +
-		   (0.7152f * col.green()) +
-		   (0.0722f * col.blue());
-}
-
-int CountLDConfigColors()
-{
-	return countof (g_LDConfigColors);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colors.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,129 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu 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 "colors.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "ldConfig.h"
+#include <QColor>
+
+static LDColor g_LDConfigColors[512];
+
+void InitColors()
+{
+	LDColorData* col;
+	print ("Initializing color information.\n");
+
+	// Always make sure there's 16 and 24 available. They're special like that.
+	col = new LDColorData;
+	col->m_faceColor =
+	col->m_hexcode = "#AAAAAA";
+	col->m_edgeColor = Qt::black;
+	g_LDConfigColors[16] = col;
+
+	col = new LDColorData;
+	col->m_faceColor =
+	col->m_edgeColor =
+	col->m_hexcode = "#000000";
+	g_LDConfigColors[24] = col;
+
+	LDConfigParser::parseLDConfig();
+}
+
+LDColor MainColor()
+{
+	return g_LDConfigColors[MainColorIndex];
+}
+
+LDColor EdgeColor()
+{
+	return g_LDConfigColors[EdgeColorIndex];
+}
+
+void LDColor::addLDConfigColor (qint32 index, LDColor color)
+{
+	assert (index >= 0 and index < countof (g_LDConfigColors));
+	g_LDConfigColors[index] = color;
+}
+
+LDColor LDColor::fromIndex (qint32 index)
+{
+	if (index < countof (g_LDConfigColors) and g_LDConfigColors[index] != null)
+		return g_LDConfigColors[index];
+
+	if (index >= 0x2000000)
+	{
+		// Direct color
+		QColor col;
+		col.setRed ((index & 0x0FF0000) >> 16);
+		col.setGreen ((index & 0x000FF00) >> 8);
+		col.setBlue (index & 0x00000FF);
+
+		if (index >= 0x3000000)
+			col.setAlpha (128);
+
+		LDColorData* color = new LDColorData;
+		color->m_name = "0x" + QString::number (index, 16).toUpper();
+		color->m_faceColor = col;
+		color->m_edgeColor = Luma (col) < 48 ? Qt::white : Qt::black;
+		color->m_hexcode = col.name();
+		color->m_index = index;
+		return LDColor (color);
+	}
+
+	return null;
+}
+
+QString LDColor::indexString() const
+{
+	if (isDirect())
+		return "0x" + QString::number (index(), 16).toUpper();
+
+	return QString::number (index());
+}
+
+bool LDColor::isDirect() const
+{
+	return index() >= 0x02000000;
+}
+
+bool LDColor::operator== (LDColor const& other)
+{
+	if ((data() == nullptr) ^ (other == nullptr))
+		return false;
+
+	if (data() != nullptr)
+		return index() == other.index();
+
+	// both are null
+	return true;
+}
+
+int Luma (const QColor& col)
+{
+	return (0.2126f * col.red()) +
+		   (0.7152f * col.green()) +
+		   (0.0722f * col.blue());
+}
+
+int CountLDConfigColors()
+{
+	return countof (g_LDConfigColors);
+}
--- a/src/configDialog.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,735 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  configDialog.cxx: Settings dialog and everything related to it.
- *  Actual configuration core is in config.cxx.
- */
-
-#include <QGridLayout>
-#include <QFileDialog>
-#include <QColorDialog>
-#include <QBoxLayout>
-#include <QKeyEvent>
-#include <QGroupBox>
-#include <QDoubleSpinBox>
-#include <QLineEdit>
-#include <QCheckBox>
-#include "main.h"
-#include "configDialog.h"
-#include "ldDocument.h"
-#include "configuration.h"
-#include "miscallenous.h"
-#include "colors.h"
-#include "colorSelector.h"
-#include "glRenderer.h"
-#include "ui_config.h"
-
-EXTERN_CFGENTRY (String, YtruderPath)
-EXTERN_CFGENTRY (String, RectifierPath)
-EXTERN_CFGENTRY (String, IntersectorPath)
-EXTERN_CFGENTRY (String, CovererPath)
-EXTERN_CFGENTRY (String, IsecalcPath)
-EXTERN_CFGENTRY (String, Edger2Path)
-EXTERN_CFGENTRY (Bool, YtruderUsesWine)
-EXTERN_CFGENTRY (Bool, RectifierUsesWine)
-EXTERN_CFGENTRY (Bool, IntersectorUsesWine)
-EXTERN_CFGENTRY (Bool, CovererUsesWine)
-EXTERN_CFGENTRY (Bool, IsecalcUsesWine)
-EXTERN_CFGENTRY (Bool, Edger2UsesWine)
-EXTERN_CFGENTRY (String, QuickColorToolbar)
-
-const char* g_extProgPathFilter =
-#ifdef _WIN32
-	"Applications (*.exe)(*.exe);;"
-#endif
-	"All files (*.*)(*.*)";
-
-//
-//
-static struct LDExtProgInfo
-{
-	QString const	name;
-	QString const	iconname;
-	QString* const	path;
-	QLineEdit*		input;
-	QPushButton*	setPathButton;
-	bool* const		wine;
-	QCheckBox*		wineBox;
-} g_LDExtProgInfo[] =
-{
-#ifndef _WIN32
-# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &cfg::NAME##Path, null, null, \
-	&cfg::NAME##UsesWine, null },
-#else
-# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &cfg::NAME##Path, null, null, null, null },
-#endif
-	EXTPROG (Ytruder, ytruder)
-	EXTPROG (Rectifier, rectifier)
-	EXTPROG (Intersector, intersector)
-	EXTPROG (Isecalc, isecalc)
-	EXTPROG (Coverer, coverer)
-	EXTPROG (Edger2, edger2)
-#undef EXTPROG
-};
-
-//
-//
-ConfigDialog::ConfigDialog (ConfigDialog::Tab deftab, QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f)
-{
-	assert (g_win != null);
-	ui = new Ui_ConfigUI;
-	ui->setupUi (this);
-
-	// Set defaults
-	m_applyToWidgetOptions ([&](QWidget* wdg, AbstractConfigEntry* conf)
-	{
-		QVariant value (conf->toVariant());
-		QLineEdit* le;
-		QSpinBox* spinbox;
-		QDoubleSpinBox* doublespinbox;
-		QSlider* slider;
-		QCheckBox* checkbox;
-		QPushButton* button;
-
-		if ((le = qobject_cast<QLineEdit*> (wdg)) != null)
-		{
-			le->setText (value.toString());
-		}
-		elif ((spinbox = qobject_cast<QSpinBox*> (wdg)) != null)
-		{
-			spinbox->setValue (value.toInt());
-		}
-		elif ((doublespinbox = qobject_cast<QDoubleSpinBox*> (wdg)) != null)
-		{
-			doublespinbox->setValue (value.toDouble());
-		}
-		elif ((slider = qobject_cast<QSlider*> (wdg)) != null)
-		{
-			slider->setValue (value.toInt());
-		}
-		elif ((checkbox = qobject_cast<QCheckBox*> (wdg)) != null)
-		{
-			checkbox->setChecked (value.toBool());
-		}
-		elif ((button = qobject_cast<QPushButton*> (wdg)) != null)
-		{
-			setButtonBackground (button, value.toString());
-			connect (button, SIGNAL (clicked()), this, SLOT (setButtonColor()));
-		}
-		else
-		{
-			print ("Unknown widget of type %1\n", wdg->metaObject()->className());
-		}
-	});
-
-	g_win->applyToActions ([&](QAction* act)
-	{
-		addShortcut (act);
-	});
-
-	ui->shortcutsList->setSortingEnabled (true);
-	ui->shortcutsList->sortItems();
-	quickColors = LoadQuickColorList();
-	updateQuickColorList();
-	initExtProgs();
-	selectPage (deftab);
-	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()));
-	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()));
-	connect (ui->findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder()));
-	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 (QAction* act)
-{
-	ShortcutListItem* item = new ShortcutListItem;
-	item->setIcon (act->icon());
-	item->setAction (act);
-	item->setSequence (act->shortcut());
-	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 (ui->shortcutsList->count(), item);
-}
-
-//
-// Initializes the stuff in the ext programs tab
-//
-void ConfigDialog::initExtProgs()
-{
-	QGridLayout* pathsLayout = new QGridLayout;
-	int row = 0;
-
-	for (LDExtProgInfo& info : g_LDExtProgInfo)
-	{
-		QLabel* icon = new QLabel,
-		*progLabel = new QLabel (info.name);
-		QLineEdit* input = new QLineEdit;
-		QPushButton* setPathButton = new QPushButton;
-
-		icon->setPixmap (GetIcon (info.iconname));
-		input->setText (*info.path);
-		setPathButton->setIcon (GetIcon ("folder"));
-		info.input = input;
-		info.setPathButton = setPathButton;
-
-		connect (setPathButton, SIGNAL (clicked()), this, SLOT (slot_setExtProgPath()));
-
-		pathsLayout->addWidget (icon, row, 0);
-		pathsLayout->addWidget (progLabel, row, 1);
-		pathsLayout->addWidget (input, row, 2);
-		pathsLayout->addWidget (setPathButton, row, 3);
-
-		if (info.wine != null)
-		{
-			QCheckBox* wineBox = new QCheckBox ("Wine");
-			wineBox->setChecked (*info.wine);
-			info.wineBox = wineBox;
-			pathsLayout->addWidget (wineBox, row, 4);
-		}
-
-		++row;
-	}
-
-	ui->extProgs->setLayout (pathsLayout);
-}
-
-void ConfigDialog::m_applyToWidgetOptions (std::function<void (QWidget*, AbstractConfigEntry*)> func)
-{
-	// Apply configuration
-	for (QWidget* widget : findChildren<QWidget*>())
-	{
-		if (not widget->objectName().startsWith ("config"))
-			continue;
-
-		QString confname (widget->objectName().mid (strlen ("config")));
-		AbstractConfigEntry* conf (Config::FindByName (confname));
-
-		if (conf == null)
-		{
-			print ("Couldn't find configuration entry named %1", confname);
-			continue;
-		}
-
-		func (widget, conf);
-	}
-}
-
-//
-// Set the settings based on widget data.
-//
-void ConfigDialog::applySettings()
-{
-	m_applyToWidgetOptions ([&](QWidget* widget, AbstractConfigEntry* conf)
-	{
-		QVariant value (conf->toVariant());
-		QLineEdit* le;
-		QSpinBox* spinbox;
-		QDoubleSpinBox* doublespinbox;
-		QSlider* slider;
-		QCheckBox* checkbox;
-		QPushButton* button;
-
-		if ((le = qobject_cast<QLineEdit*> (widget)) != null)
-			value = le->text();
-		elif ((spinbox = qobject_cast<QSpinBox*> (widget)) != null)
-			value = spinbox->value();
-		elif ((doublespinbox = qobject_cast<QDoubleSpinBox*> (widget)) != null)
-			value = doublespinbox->value();
-		elif ((slider = qobject_cast<QSlider*> (widget)) != null)
-			value = slider->value();
-		elif ((checkbox = qobject_cast<QCheckBox*> (widget)) != null)
-			value = checkbox->isChecked();
-		elif ((button = qobject_cast<QPushButton*> (widget)) != null)
-			value = m_buttonColors[button];
-		else
-			print ("Unknown widget of type %1\n", widget->metaObject()->className());
-
-		conf->loadFromVariant (value);
-	});
-
-	// Rebuild the quick color toolbar
-	g_win->setQuickColors (quickColors);
-	cfg::QuickColorToolbar = quickColorString();
-
-	// Ext program settings
-	for (const LDExtProgInfo& info : g_LDExtProgInfo)
-	{
-		*info.path = info.input->text();
-
-		if (info.wine != null)
-			*info.wine = info.wineBox->isChecked();
-	}
-
-	// Apply shortcuts
-	for (int i = 0; i < ui->shortcutsList->count(); ++i)
-	{
-		auto item = static_cast<ShortcutListItem*> (ui->shortcutsList->item (i));
-		item->action()->setShortcut (item->sequence());
-	}
-
-	Config::Save();
-	LDDocument::current()->reloadAllSubfiles();
-	LoadLogoStuds();
-	g_win->R()->setBackground();
-	g_win->doFullRefresh();
-	g_win->updateDocumentList();
-}
-
-//
-// A dialog button was clicked
-//
-void ConfigDialog::buttonClicked (QAbstractButton* button)
-{
-	QDialogButtonBox* dbb = ui->buttonBox;
-
-	if (button == dbb->button (QDialogButtonBox::Ok))
-	{
-		applySettings();
-		accept();
-	}
-	elif (button == dbb->button (QDialogButtonBox::Apply))
-	{
-		applySettings();
-	}
-	elif (button == dbb->button (QDialogButtonBox::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 ("<hr />");
-			item->setIcon (GetIcon ("empty"));
-		}
-		else
-		{
-			LDColor col (entry.color());
-
-			if (col == null)
-			{
-				item->setText ("[[unknown color]]");
-				item->setIcon (GetIcon ("error"));
-			}
-			else
-			{
-				item->setText (col.name());
-				item->setIcon (MakeColorIcon (col, 16));
-			}
-		}
-
-		ui->quickColorList->addItem (item);
-		quickColorItems << item;
-
-		if (sel and &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 (not isNew)
-	{
-		item = getSelectedQuickColor();
-
-		if (not item)
-			return;
-
-		int i = getItemRow (item, quickColorItems);
-		entry = &quickColors[i];
-
-		if (entry->isSeparator() == true)
-			return; // don't color separators
-	}
-
-	LDColor defval = entry ? entry->color() : null;
-	LDColor val;
-
-	if (not ColorSelector::selectColor (val, defval, this))
-		return;
-
-	if (entry != null)
-	{
-		entry->setColor (val);
-	}
-	else
-	{
-		LDQuickColor entry (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 or dest >= quickColorItems.size())
-		return; // destination out of bounds
-
-	qSwap (quickColors[dest], quickColors[idx]);
-	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();
-}
-
-//
-//
-void ConfigDialog::setButtonColor()
-{
-	QPushButton* button = qobject_cast<QPushButton*> (sender());
-
-	if (button == null)
-	{
-		print ("setButtonColor: null sender!\n");
-		return;
-	}
-
-	QColor color = QColorDialog::getColor (m_buttonColors[button]);
-
-	if (color.isValid())
-	{
-		QString colorname;
-		colorname.sprintf ("#%.2X%.2X%.2X", color.red(), color.green(), color.blue());
-		setButtonBackground (button, colorname);
-	}
-}
-
-//
-// Sets background color of a given button.
-//
-void ConfigDialog::setButtonBackground (QPushButton* button, QString value)
-{
-	button->setIcon (GetIcon ("colorselect"));
-	button->setAutoFillBackground (true);
-	button->setStyleSheet (format ("background-color: %1", value));
-	m_buttonColors[button] = QColor (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, this))
-		setShortcutText (item);
-}
-
-//
-// Reset a shortcut to defaults
-//
-void ConfigDialog::slot_resetShortcut()
-{
-	QList<ShortcutListItem*> sel = getShortcutSelection();
-
-	for (ShortcutListItem* item : sel)
-	{
-		item->setSequence (MainWindow::defaultShortcut (item->action()));
-		setShortcutText (item);
-	}
-}
-
-//
-// Remove the shortcut of an action.
-//
-void ConfigDialog::slot_clearShortcut()
-{
-	QList<ShortcutListItem*> sel = getShortcutSelection();
-
-	for (ShortcutListItem* item : sel)
-	{
-		item->setSequence (QKeySequence());
-		setShortcutText (item);
-	}
-}
-
-//
-// Set the path of an external program
-//
-void ConfigDialog::slot_setExtProgPath()
-{
-	const LDExtProgInfo* info = null;
-
-	for (const LDExtProgInfo& it : g_LDExtProgInfo)
-	{
-		if (it.setPathButton == sender())
-		{
-			info = &it;
-			break;
-		}
-	}
-
-	assert (info != null);
-	QString fpath = QFileDialog::getOpenFileName (this, format ("Path to %1", info->name), *info->path, g_extProgPathFilter);
-
-	if (fpath.isEmpty())
-		return;
-
-	info->input->setText (fpath);
-}
-
-//
-// '...' button pressed for the download path
-//
-void ConfigDialog::slot_findDownloadFolder()
-{
-	QString dpath = QFileDialog::getExistingDirectory();
-
-	if (not dpath.isEmpty())
-		ui->configDownloadFilePath->setText (dpath);
-}
-
-//
-//
-// Updates the text string for a given shortcut list item
-//
-void ConfigDialog::setShortcutText (ShortcutListItem* item)
-{
-	QAction* act = item->action();
-	QString label = act->iconText();
-	QString keybind = item->sequence().toString();
-	item->setText (format ("%1 (%2)", label, keybind));
-}
-
-//
-// Gets the configuration string of the quick color toolbar
-//
-QString ConfigDialog::quickColorString()
-{
-	QString val;
-
-	for (const LDQuickColor& entry : quickColors)
-	{
-		if (val.length() > 0)
-			val += ':';
-
-		if (entry.isSeparator())
-			val += '|';
-		else
-			val += format ("%1", entry.color().index());
-	}
-
-	return val;
-}
-
-//
-//
-KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f), seq (seq)
-{
-	lb_output = new QLabel;
-	IMPLEMENT_DIALOG_BUTTONS
-
-	setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a "
-		"shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to "
-		"dismiss."));
-
-	QVBoxLayout* layout = new QVBoxLayout;
-	layout->addWidget (lb_output);
-	layout->addWidget (bbx_buttons);
-	setLayout (layout);
-
-	updateOutput();
-}
-
-//
-//
-bool KeySequenceDialog::staticDialog (ShortcutListItem* item, QWidget* parent)
-{
-	KeySequenceDialog dlg (item->sequence(), parent);
-
-	if (dlg.exec() == QDialog::Rejected)
-		return false;
-
-	item->setSequence (dlg.seq);
-	return true;
-}
-
-//
-//
-void KeySequenceDialog::updateOutput()
-{
-	QString shortcut = seq.toString();
-
-	if (seq == QKeySequence())
-		shortcut = "&lt;empty&gt;";
-
-	QString text = format ("<center><b>%1</b></center>", shortcut);
-	lb_output->setText (text);
-}
-
-//
-//
-void KeySequenceDialog::keyPressEvent (QKeyEvent* ev)
-{
-	seq = ev->key() + ev->modifiers();
-	updateOutput();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/configDialog.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,735 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  configDialog.cxx: Settings dialog and everything related to it.
+ *  Actual configuration core is in config.cxx.
+ */
+
+#include <QGridLayout>
+#include <QFileDialog>
+#include <QColorDialog>
+#include <QBoxLayout>
+#include <QKeyEvent>
+#include <QGroupBox>
+#include <QDoubleSpinBox>
+#include <QLineEdit>
+#include <QCheckBox>
+#include "main.h"
+#include "configDialog.h"
+#include "ldDocument.h"
+#include "configuration.h"
+#include "miscallenous.h"
+#include "colors.h"
+#include "colorSelector.h"
+#include "glRenderer.h"
+#include "ui_config.h"
+
+EXTERN_CFGENTRY (String, YtruderPath)
+EXTERN_CFGENTRY (String, RectifierPath)
+EXTERN_CFGENTRY (String, IntersectorPath)
+EXTERN_CFGENTRY (String, CovererPath)
+EXTERN_CFGENTRY (String, IsecalcPath)
+EXTERN_CFGENTRY (String, Edger2Path)
+EXTERN_CFGENTRY (Bool, YtruderUsesWine)
+EXTERN_CFGENTRY (Bool, RectifierUsesWine)
+EXTERN_CFGENTRY (Bool, IntersectorUsesWine)
+EXTERN_CFGENTRY (Bool, CovererUsesWine)
+EXTERN_CFGENTRY (Bool, IsecalcUsesWine)
+EXTERN_CFGENTRY (Bool, Edger2UsesWine)
+EXTERN_CFGENTRY (String, QuickColorToolbar)
+
+const char* g_extProgPathFilter =
+#ifdef _WIN32
+	"Applications (*.exe)(*.exe);;"
+#endif
+	"All files (*.*)(*.*)";
+
+//
+//
+static struct LDExtProgInfo
+{
+	QString const	name;
+	QString const	iconname;
+	QString* const	path;
+	QLineEdit*		input;
+	QPushButton*	setPathButton;
+	bool* const		wine;
+	QCheckBox*		wineBox;
+} g_LDExtProgInfo[] =
+{
+#ifndef _WIN32
+# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &cfg::NAME##Path, null, null, \
+	&cfg::NAME##UsesWine, null },
+#else
+# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &cfg::NAME##Path, null, null, null, null },
+#endif
+	EXTPROG (Ytruder, ytruder)
+	EXTPROG (Rectifier, rectifier)
+	EXTPROG (Intersector, intersector)
+	EXTPROG (Isecalc, isecalc)
+	EXTPROG (Coverer, coverer)
+	EXTPROG (Edger2, edger2)
+#undef EXTPROG
+};
+
+//
+//
+ConfigDialog::ConfigDialog (ConfigDialog::Tab deftab, QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f)
+{
+	assert (g_win != null);
+	ui = new Ui_ConfigUI;
+	ui->setupUi (this);
+
+	// Set defaults
+	m_applyToWidgetOptions ([&](QWidget* wdg, AbstractConfigEntry* conf)
+	{
+		QVariant value (conf->toVariant());
+		QLineEdit* le;
+		QSpinBox* spinbox;
+		QDoubleSpinBox* doublespinbox;
+		QSlider* slider;
+		QCheckBox* checkbox;
+		QPushButton* button;
+
+		if ((le = qobject_cast<QLineEdit*> (wdg)) != null)
+		{
+			le->setText (value.toString());
+		}
+		elif ((spinbox = qobject_cast<QSpinBox*> (wdg)) != null)
+		{
+			spinbox->setValue (value.toInt());
+		}
+		elif ((doublespinbox = qobject_cast<QDoubleSpinBox*> (wdg)) != null)
+		{
+			doublespinbox->setValue (value.toDouble());
+		}
+		elif ((slider = qobject_cast<QSlider*> (wdg)) != null)
+		{
+			slider->setValue (value.toInt());
+		}
+		elif ((checkbox = qobject_cast<QCheckBox*> (wdg)) != null)
+		{
+			checkbox->setChecked (value.toBool());
+		}
+		elif ((button = qobject_cast<QPushButton*> (wdg)) != null)
+		{
+			setButtonBackground (button, value.toString());
+			connect (button, SIGNAL (clicked()), this, SLOT (setButtonColor()));
+		}
+		else
+		{
+			print ("Unknown widget of type %1\n", wdg->metaObject()->className());
+		}
+	});
+
+	g_win->applyToActions ([&](QAction* act)
+	{
+		addShortcut (act);
+	});
+
+	ui->shortcutsList->setSortingEnabled (true);
+	ui->shortcutsList->sortItems();
+	quickColors = LoadQuickColorList();
+	updateQuickColorList();
+	initExtProgs();
+	selectPage (deftab);
+	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()));
+	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()));
+	connect (ui->findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder()));
+	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 (QAction* act)
+{
+	ShortcutListItem* item = new ShortcutListItem;
+	item->setIcon (act->icon());
+	item->setAction (act);
+	item->setSequence (act->shortcut());
+	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 (ui->shortcutsList->count(), item);
+}
+
+//
+// Initializes the stuff in the ext programs tab
+//
+void ConfigDialog::initExtProgs()
+{
+	QGridLayout* pathsLayout = new QGridLayout;
+	int row = 0;
+
+	for (LDExtProgInfo& info : g_LDExtProgInfo)
+	{
+		QLabel* icon = new QLabel,
+		*progLabel = new QLabel (info.name);
+		QLineEdit* input = new QLineEdit;
+		QPushButton* setPathButton = new QPushButton;
+
+		icon->setPixmap (GetIcon (info.iconname));
+		input->setText (*info.path);
+		setPathButton->setIcon (GetIcon ("folder"));
+		info.input = input;
+		info.setPathButton = setPathButton;
+
+		connect (setPathButton, SIGNAL (clicked()), this, SLOT (slot_setExtProgPath()));
+
+		pathsLayout->addWidget (icon, row, 0);
+		pathsLayout->addWidget (progLabel, row, 1);
+		pathsLayout->addWidget (input, row, 2);
+		pathsLayout->addWidget (setPathButton, row, 3);
+
+		if (info.wine != null)
+		{
+			QCheckBox* wineBox = new QCheckBox ("Wine");
+			wineBox->setChecked (*info.wine);
+			info.wineBox = wineBox;
+			pathsLayout->addWidget (wineBox, row, 4);
+		}
+
+		++row;
+	}
+
+	ui->extProgs->setLayout (pathsLayout);
+}
+
+void ConfigDialog::m_applyToWidgetOptions (std::function<void (QWidget*, AbstractConfigEntry*)> func)
+{
+	// Apply configuration
+	for (QWidget* widget : findChildren<QWidget*>())
+	{
+		if (not widget->objectName().startsWith ("config"))
+			continue;
+
+		QString confname (widget->objectName().mid (strlen ("config")));
+		AbstractConfigEntry* conf (Config::FindByName (confname));
+
+		if (conf == null)
+		{
+			print ("Couldn't find configuration entry named %1", confname);
+			continue;
+		}
+
+		func (widget, conf);
+	}
+}
+
+//
+// Set the settings based on widget data.
+//
+void ConfigDialog::applySettings()
+{
+	m_applyToWidgetOptions ([&](QWidget* widget, AbstractConfigEntry* conf)
+	{
+		QVariant value (conf->toVariant());
+		QLineEdit* le;
+		QSpinBox* spinbox;
+		QDoubleSpinBox* doublespinbox;
+		QSlider* slider;
+		QCheckBox* checkbox;
+		QPushButton* button;
+
+		if ((le = qobject_cast<QLineEdit*> (widget)) != null)
+			value = le->text();
+		elif ((spinbox = qobject_cast<QSpinBox*> (widget)) != null)
+			value = spinbox->value();
+		elif ((doublespinbox = qobject_cast<QDoubleSpinBox*> (widget)) != null)
+			value = doublespinbox->value();
+		elif ((slider = qobject_cast<QSlider*> (widget)) != null)
+			value = slider->value();
+		elif ((checkbox = qobject_cast<QCheckBox*> (widget)) != null)
+			value = checkbox->isChecked();
+		elif ((button = qobject_cast<QPushButton*> (widget)) != null)
+			value = m_buttonColors[button];
+		else
+			print ("Unknown widget of type %1\n", widget->metaObject()->className());
+
+		conf->loadFromVariant (value);
+	});
+
+	// Rebuild the quick color toolbar
+	g_win->setQuickColors (quickColors);
+	cfg::QuickColorToolbar = quickColorString();
+
+	// Ext program settings
+	for (const LDExtProgInfo& info : g_LDExtProgInfo)
+	{
+		*info.path = info.input->text();
+
+		if (info.wine != null)
+			*info.wine = info.wineBox->isChecked();
+	}
+
+	// Apply shortcuts
+	for (int i = 0; i < ui->shortcutsList->count(); ++i)
+	{
+		auto item = static_cast<ShortcutListItem*> (ui->shortcutsList->item (i));
+		item->action()->setShortcut (item->sequence());
+	}
+
+	Config::Save();
+	LDDocument::current()->reloadAllSubfiles();
+	LoadLogoStuds();
+	g_win->R()->setBackground();
+	g_win->doFullRefresh();
+	g_win->updateDocumentList();
+}
+
+//
+// A dialog button was clicked
+//
+void ConfigDialog::buttonClicked (QAbstractButton* button)
+{
+	QDialogButtonBox* dbb = ui->buttonBox;
+
+	if (button == dbb->button (QDialogButtonBox::Ok))
+	{
+		applySettings();
+		accept();
+	}
+	elif (button == dbb->button (QDialogButtonBox::Apply))
+	{
+		applySettings();
+	}
+	elif (button == dbb->button (QDialogButtonBox::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 ("<hr />");
+			item->setIcon (GetIcon ("empty"));
+		}
+		else
+		{
+			LDColor col (entry.color());
+
+			if (col == null)
+			{
+				item->setText ("[[unknown color]]");
+				item->setIcon (GetIcon ("error"));
+			}
+			else
+			{
+				item->setText (col.name());
+				item->setIcon (MakeColorIcon (col, 16));
+			}
+		}
+
+		ui->quickColorList->addItem (item);
+		quickColorItems << item;
+
+		if (sel and &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 (not isNew)
+	{
+		item = getSelectedQuickColor();
+
+		if (not item)
+			return;
+
+		int i = getItemRow (item, quickColorItems);
+		entry = &quickColors[i];
+
+		if (entry->isSeparator() == true)
+			return; // don't color separators
+	}
+
+	LDColor defval = entry ? entry->color() : null;
+	LDColor val;
+
+	if (not ColorSelector::selectColor (val, defval, this))
+		return;
+
+	if (entry != null)
+	{
+		entry->setColor (val);
+	}
+	else
+	{
+		LDQuickColor entry (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 or dest >= quickColorItems.size())
+		return; // destination out of bounds
+
+	qSwap (quickColors[dest], quickColors[idx]);
+	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();
+}
+
+//
+//
+void ConfigDialog::setButtonColor()
+{
+	QPushButton* button = qobject_cast<QPushButton*> (sender());
+
+	if (button == null)
+	{
+		print ("setButtonColor: null sender!\n");
+		return;
+	}
+
+	QColor color = QColorDialog::getColor (m_buttonColors[button]);
+
+	if (color.isValid())
+	{
+		QString colorname;
+		colorname.sprintf ("#%.2X%.2X%.2X", color.red(), color.green(), color.blue());
+		setButtonBackground (button, colorname);
+	}
+}
+
+//
+// Sets background color of a given button.
+//
+void ConfigDialog::setButtonBackground (QPushButton* button, QString value)
+{
+	button->setIcon (GetIcon ("colorselect"));
+	button->setAutoFillBackground (true);
+	button->setStyleSheet (format ("background-color: %1", value));
+	m_buttonColors[button] = QColor (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, this))
+		setShortcutText (item);
+}
+
+//
+// Reset a shortcut to defaults
+//
+void ConfigDialog::slot_resetShortcut()
+{
+	QList<ShortcutListItem*> sel = getShortcutSelection();
+
+	for (ShortcutListItem* item : sel)
+	{
+		item->setSequence (MainWindow::defaultShortcut (item->action()));
+		setShortcutText (item);
+	}
+}
+
+//
+// Remove the shortcut of an action.
+//
+void ConfigDialog::slot_clearShortcut()
+{
+	QList<ShortcutListItem*> sel = getShortcutSelection();
+
+	for (ShortcutListItem* item : sel)
+	{
+		item->setSequence (QKeySequence());
+		setShortcutText (item);
+	}
+}
+
+//
+// Set the path of an external program
+//
+void ConfigDialog::slot_setExtProgPath()
+{
+	const LDExtProgInfo* info = null;
+
+	for (const LDExtProgInfo& it : g_LDExtProgInfo)
+	{
+		if (it.setPathButton == sender())
+		{
+			info = &it;
+			break;
+		}
+	}
+
+	assert (info != null);
+	QString fpath = QFileDialog::getOpenFileName (this, format ("Path to %1", info->name), *info->path, g_extProgPathFilter);
+
+	if (fpath.isEmpty())
+		return;
+
+	info->input->setText (fpath);
+}
+
+//
+// '...' button pressed for the download path
+//
+void ConfigDialog::slot_findDownloadFolder()
+{
+	QString dpath = QFileDialog::getExistingDirectory();
+
+	if (not dpath.isEmpty())
+		ui->configDownloadFilePath->setText (dpath);
+}
+
+//
+//
+// Updates the text string for a given shortcut list item
+//
+void ConfigDialog::setShortcutText (ShortcutListItem* item)
+{
+	QAction* act = item->action();
+	QString label = act->iconText();
+	QString keybind = item->sequence().toString();
+	item->setText (format ("%1 (%2)", label, keybind));
+}
+
+//
+// Gets the configuration string of the quick color toolbar
+//
+QString ConfigDialog::quickColorString()
+{
+	QString val;
+
+	for (const LDQuickColor& entry : quickColors)
+	{
+		if (val.length() > 0)
+			val += ':';
+
+		if (entry.isSeparator())
+			val += '|';
+		else
+			val += format ("%1", entry.color().index());
+	}
+
+	return val;
+}
+
+//
+//
+KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f), seq (seq)
+{
+	lb_output = new QLabel;
+	IMPLEMENT_DIALOG_BUTTONS
+
+	setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a "
+		"shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to "
+		"dismiss."));
+
+	QVBoxLayout* layout = new QVBoxLayout;
+	layout->addWidget (lb_output);
+	layout->addWidget (bbx_buttons);
+	setLayout (layout);
+
+	updateOutput();
+}
+
+//
+//
+bool KeySequenceDialog::staticDialog (ShortcutListItem* item, QWidget* parent)
+{
+	KeySequenceDialog dlg (item->sequence(), parent);
+
+	if (dlg.exec() == QDialog::Rejected)
+		return false;
+
+	item->setSequence (dlg.seq);
+	return true;
+}
+
+//
+//
+void KeySequenceDialog::updateOutput()
+{
+	QString shortcut = seq.toString();
+
+	if (seq == QKeySequence())
+		shortcut = "&lt;empty&gt;";
+
+	QString text = format ("<center><b>%1</b></center>", shortcut);
+	lb_output->setText (text);
+}
+
+//
+//
+void KeySequenceDialog::keyPressEvent (QKeyEvent* ev)
+{
+	seq = ev->key() + ev->modifiers();
+	updateOutput();
+}
--- a/src/configuration.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,230 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  config.cxx: Configuration management. I don't like how unsafe QSettings
- *  is so this implements a type-safer and identifer-safer wrapping system of
- *  configuration variables. QSettings is used underlyingly, this is a matter
- *  of interface.
- */
-
-#include <errno.h>
-#include <QDir>
-#include <QTextStream>
-#include <QSettings>
-#include "main.h"
-#include "configuration.h"
-#include "miscallenous.h"
-#include "mainWindow.h"
-#include "ldDocument.h"
-#include "glRenderer.h"
-#include "configuration.inc"
-
-#ifdef _WIN32
-# define EXTENSION ".ini"
-#else
-# define EXTENSION ".cfg"
-#endif // _WIN32
-
-#define MAX_CONFIG 512
-
-static QMap<QString, AbstractConfigEntry*>	EntriesByName;
-static QList<AbstractConfigEntry*>			ConfigurationEntries;
-
-AbstractConfigEntry::AbstractConfigEntry (QString name) :
-	m_name (name) {}
-
-void Config::Initialize()
-{
-	SetupConfigurationLists();
-	print ("Configuration initialized with %1 entries\n", ConfigurationEntries.size());
-}
-
-static void InitConfigurationEntry (AbstractConfigEntry* entry)
-{
-	ConfigurationEntries << entry;
-	EntriesByName[entry->name()] = entry;
-}
-
-//
-// Load the configuration from file
-//
-bool Config::Load()
-{
-	QSettings* settings = SettingsObject();
-	print ("Loading configuration file from %1\n", settings->fileName());
-
-	for (AbstractConfigEntry* cfg : ConfigurationEntries)
-	{
-		if (cfg == null)
-			break;
-
-		QVariant val = settings->value (cfg->name(), cfg->getDefaultAsVariant());
-		cfg->loadFromVariant (val);
-	}
-
-	if (g_win != null)
-		g_win->loadShortcuts (settings);
-
-	delete settings;
-	return true;
-}
-
-//
-// Save the configuration to disk
-//
-bool Config::Save()
-{
-	QSettings* settings = SettingsObject();
-
-	for (AbstractConfigEntry* cfg : ConfigurationEntries)
-	{
-		if (not cfg->isDefault())
-			settings->setValue (cfg->name(), cfg->toVariant());
-		else
-			settings->remove (cfg->name());
-	}
-
-	if (g_win != null)
-		g_win->saveShortcuts (settings);
-
-	settings->sync();
-	print ("Configuration saved to %1.\n", settings->fileName());
-	delete settings;
-	return true;
-}
-
-//
-// Reset configuration to defaults.
-//
-void Config::ResetToDefaults()
-{
-	for (AbstractConfigEntry* cfg : ConfigurationEntries)
-		cfg->resetValue();
-}
-
-//
-// Where is the configuration file located at?
-//
-QString Config::FilePath (QString file)
-{
-	return Config::DirectoryPath() + DIRSLASH + file;
-}
-
-//
-// Directory of the configuration file.
-//
-QString Config::DirectoryPath()
-{
-	QSettings* settings = SettingsObject();
-	QString result = Dirname (settings->fileName());
-	delete settings;
-	return result;
-}
-
-//
-// Accessor to the settings object
-//
-QSettings* Config::SettingsObject()
-{
-	QString path = qApp->applicationDirPath() + "/" UNIXNAME EXTENSION;
-	return new QSettings (path, QSettings::IniFormat);
-}
-
-//
-// Accessor to entry list
-//
-QList<AbstractConfigEntry*> const& Config::AllConfigEntries()
-{
-	return ConfigurationEntries;
-}
-
-AbstractConfigEntry* Config::FindByName (QString const& name)
-{
-	auto it = EntriesByName.find (name);
-	return (it != EntriesByName.end()) ? *it : null;
-}
-
-template<typename T>
-static T* GetConfigByName (QString name, AbstractConfigEntry::Type type)
-{
-	auto it = EntriesByName.find (name);
-
-	if (it == EntriesByName.end())
-		return null;
-
-	AbstractConfigEntry* cfg = it.value();
-
-	if (cfg->getType() != type)
-	{
-		fprint (stderr, "type of %1 is %2, not %3\n", name, cfg->getType(), type);
-		abort();
-	}
-
-	return reinterpret_cast<T*> (cfg);
-}
-
-#undef IMPLEMENT_CONFIG
-
-#define IMPLEMENT_CONFIG(NAME)												\
-	NAME##ConfigEntry* NAME##ConfigEntry::getByName (QString name)			\
-	{																		\
-		return GetConfigByName<NAME##ConfigEntry> (name, E##NAME##Type);	\
-	}
-
-IMPLEMENT_CONFIG (Int)
-IMPLEMENT_CONFIG (String)
-IMPLEMENT_CONFIG (Bool)
-IMPLEMENT_CONFIG (Float)
-IMPLEMENT_CONFIG (List)
-IMPLEMENT_CONFIG (KeySequence)
-IMPLEMENT_CONFIG (Vertex)
-
-void IntConfigEntry::loadFromVariant (const QVariant& val)
-{
-	*m_valueptr = val.toInt();
-}
-
-void StringConfigEntry::loadFromVariant (const QVariant& val)
-{
-	*m_valueptr = val.toString();
-}
-
-void BoolConfigEntry::loadFromVariant (const QVariant& val)
-{
-	*m_valueptr = val.toBool();
-}
-
-void ListConfigEntry::loadFromVariant (const QVariant& val)
-{
-	*m_valueptr = val.toList();
-}
-
-void KeySequenceConfigEntry::loadFromVariant (const QVariant& val)
-{
-	*m_valueptr = val.toString();
-}
-
-void FloatConfigEntry::loadFromVariant (const QVariant& val)
-{
-	*m_valueptr = val.toDouble();
-}
-
-void VertexConfigEntry::loadFromVariant (const QVariant& val)
-{
-	*m_valueptr = val.value<Vertex>();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/configuration.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,230 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  =====================================================================
+ *
+ *  config.cxx: Configuration management. I don't like how unsafe QSettings
+ *  is so this implements a type-safer and identifer-safer wrapping system of
+ *  configuration variables. QSettings is used underlyingly, this is a matter
+ *  of interface.
+ */
+
+#include <errno.h>
+#include <QDir>
+#include <QTextStream>
+#include <QSettings>
+#include "main.h"
+#include "configuration.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "glRenderer.h"
+#include "configuration.inc"
+
+#ifdef _WIN32
+# define EXTENSION ".ini"
+#else
+# define EXTENSION ".cfg"
+#endif // _WIN32
+
+#define MAX_CONFIG 512
+
+static QMap<QString, AbstractConfigEntry*>	EntriesByName;
+static QList<AbstractConfigEntry*>			ConfigurationEntries;
+
+AbstractConfigEntry::AbstractConfigEntry (QString name) :
+	m_name (name) {}
+
+void Config::Initialize()
+{
+	SetupConfigurationLists();
+	print ("Configuration initialized with %1 entries\n", ConfigurationEntries.size());
+}
+
+static void InitConfigurationEntry (AbstractConfigEntry* entry)
+{
+	ConfigurationEntries << entry;
+	EntriesByName[entry->name()] = entry;
+}
+
+//
+// Load the configuration from file
+//
+bool Config::Load()
+{
+	QSettings* settings = SettingsObject();
+	print ("Loading configuration file from %1\n", settings->fileName());
+
+	for (AbstractConfigEntry* cfg : ConfigurationEntries)
+	{
+		if (cfg == null)
+			break;
+
+		QVariant val = settings->value (cfg->name(), cfg->getDefaultAsVariant());
+		cfg->loadFromVariant (val);
+	}
+
+	if (g_win != null)
+		g_win->loadShortcuts (settings);
+
+	delete settings;
+	return true;
+}
+
+//
+// Save the configuration to disk
+//
+bool Config::Save()
+{
+	QSettings* settings = SettingsObject();
+
+	for (AbstractConfigEntry* cfg : ConfigurationEntries)
+	{
+		if (not cfg->isDefault())
+			settings->setValue (cfg->name(), cfg->toVariant());
+		else
+			settings->remove (cfg->name());
+	}
+
+	if (g_win != null)
+		g_win->saveShortcuts (settings);
+
+	settings->sync();
+	print ("Configuration saved to %1.\n", settings->fileName());
+	delete settings;
+	return true;
+}
+
+//
+// Reset configuration to defaults.
+//
+void Config::ResetToDefaults()
+{
+	for (AbstractConfigEntry* cfg : ConfigurationEntries)
+		cfg->resetValue();
+}
+
+//
+// Where is the configuration file located at?
+//
+QString Config::FilePath (QString file)
+{
+	return Config::DirectoryPath() + DIRSLASH + file;
+}
+
+//
+// Directory of the configuration file.
+//
+QString Config::DirectoryPath()
+{
+	QSettings* settings = SettingsObject();
+	QString result = Dirname (settings->fileName());
+	delete settings;
+	return result;
+}
+
+//
+// Accessor to the settings object
+//
+QSettings* Config::SettingsObject()
+{
+	QString path = qApp->applicationDirPath() + "/" UNIXNAME EXTENSION;
+	return new QSettings (path, QSettings::IniFormat);
+}
+
+//
+// Accessor to entry list
+//
+QList<AbstractConfigEntry*> const& Config::AllConfigEntries()
+{
+	return ConfigurationEntries;
+}
+
+AbstractConfigEntry* Config::FindByName (QString const& name)
+{
+	auto it = EntriesByName.find (name);
+	return (it != EntriesByName.end()) ? *it : null;
+}
+
+template<typename T>
+static T* GetConfigByName (QString name, AbstractConfigEntry::Type type)
+{
+	auto it = EntriesByName.find (name);
+
+	if (it == EntriesByName.end())
+		return null;
+
+	AbstractConfigEntry* cfg = it.value();
+
+	if (cfg->getType() != type)
+	{
+		fprint (stderr, "type of %1 is %2, not %3\n", name, cfg->getType(), type);
+		abort();
+	}
+
+	return reinterpret_cast<T*> (cfg);
+}
+
+#undef IMPLEMENT_CONFIG
+
+#define IMPLEMENT_CONFIG(NAME)												\
+	NAME##ConfigEntry* NAME##ConfigEntry::getByName (QString name)			\
+	{																		\
+		return GetConfigByName<NAME##ConfigEntry> (name, E##NAME##Type);	\
+	}
+
+IMPLEMENT_CONFIG (Int)
+IMPLEMENT_CONFIG (String)
+IMPLEMENT_CONFIG (Bool)
+IMPLEMENT_CONFIG (Float)
+IMPLEMENT_CONFIG (List)
+IMPLEMENT_CONFIG (KeySequence)
+IMPLEMENT_CONFIG (Vertex)
+
+void IntConfigEntry::loadFromVariant (const QVariant& val)
+{
+	*m_valueptr = val.toInt();
+}
+
+void StringConfigEntry::loadFromVariant (const QVariant& val)
+{
+	*m_valueptr = val.toString();
+}
+
+void BoolConfigEntry::loadFromVariant (const QVariant& val)
+{
+	*m_valueptr = val.toBool();
+}
+
+void ListConfigEntry::loadFromVariant (const QVariant& val)
+{
+	*m_valueptr = val.toList();
+}
+
+void KeySequenceConfigEntry::loadFromVariant (const QVariant& val)
+{
+	*m_valueptr = val.toString();
+}
+
+void FloatConfigEntry::loadFromVariant (const QVariant& val)
+{
+	*m_valueptr = val.toDouble();
+}
+
+void VertexConfigEntry::loadFromVariant (const QVariant& val)
+{
+	*m_valueptr = val.value<Vertex>();
+}
--- a/src/crashCatcher.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu 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 <unistd.h>
-#include <signal.h>
-#include "crashCatcher.h"
-#include "dialogs.h"
- 
-#ifdef __unix__
-# ifdef Q_OS_LINUX
-#  include <sys/prctl.h>
-# endif
-
-// Is the crash catcher active now?
-static bool IsCrashCatcherActive = false;
-
-// If an assertion failed, what was it?
-static QString AssertionFailureText;
-
-// List of signals to catch and crash on
-static QList<int> SignalsToCatch ({
-	SIGSEGV, // segmentation fault
-	SIGABRT, // abort() calls
-	SIGFPE, // floating point exceptions (e.g. division by zero)
-	SIGILL, // illegal instructions
-});
-
-// -------------------------------------------------------------------------------------------------
-//
-//	Removes the signal handler from SIGABRT and then aborts.
-//
-static void FinalAbort()
-{
-	struct sigaction sighandler;
-	sighandler.sa_handler = SIG_DFL;
-	sighandler.sa_flags = 0;
-	sigaction (SIGABRT, &sighandler, 0);
-	abort();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-static void HandleCrash (int sig)
-{
-	printf ("!! Caught signal %d, launching gdb\n", sig);
-
-	if (IsCrashCatcherActive)
-	{
-		printf ("Caught signal while crash catcher is active! Execution cannot continue.\n");
-		FinalAbort();
-	}
-
-	pid_t const pid (getpid());
-	QProcess proc;
-	QTemporaryFile commandsFile;
-
-	IsCrashCatcherActive = true;
-
-	if (commandsFile.open())
-	{
-		commandsFile.write (format ("attach %1\n", pid).toLocal8Bit());
-		commandsFile.write (QString ("backtrace full\n").toLocal8Bit());
-		commandsFile.write (QString ("detach\n").toLocal8Bit());
-		commandsFile.write (QString ("quit").toLocal8Bit());
-		commandsFile.close();
-	}
-
-	proc.start ("gdb", {"-x", commandsFile.fileName()});
-
-	// Linux doesn't allow ptrace to be used on anything but direct child processes
-	// so we need to use prctl to register an exception to this to allow GDB attach to us.
-	// We need to do this now and no earlier because only now we actually know GDB's PID.
-#ifdef Q_OS_LINUX
-	prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0);
-#endif
-
-	proc.waitForFinished (1000);
-	QString output (proc.readAllStandardOutput());
-	QString err (proc.readAllStandardError());
-	QFile f (UNIXNAME "-crash.log");
-
-	if (f.open (QIODevice::WriteOnly))
-	{
-		fprint (f, format ("=== Program crashed with signal %1 ===\n\n%2"
-			"GDB stdout:\n%3\nGDB stderr:\n%4\n", sig,
-			(not AssertionFailureText.isEmpty()) ? AssertionFailureText + "\n\n" : "",
-			output, err));
-		f.close();
-	}
-
-	if (not AssertionFailureText.isEmpty())
-		printf ("Assertion failed: \"%s\".\n", qPrintable (AssertionFailureText));
-
-	printf ("Backtrace written to " UNIXNAME "-crash.log. Aborting.\n");
-	FinalAbort();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-//	Initializes the crash catcher.
-//
-void InitCrashCatcher()
-{
-	struct sigaction sighandler;
-	sighandler.sa_handler = &HandleCrash;
-	sighandler.sa_flags = 0;
-	sigemptyset (&sighandler.sa_mask);
-
-	for (int sig : SignalsToCatch)
-	{
-		if (sigaction (sig, &sighandler, null) == -1)
-		{
-			fprint (stderr, "Couldn't set signal handler %1: %2", sig, strerror (errno));
-			SignalsToCatch.removeOne (sig);
-		}
-	}
-
-	print ("Crash catcher hooked to signals: %1\n", SignalsToCatch);
-}
-
-#endif // #ifdef __unix__
-
-// -------------------------------------------------------------------------------------------------
-//
-// This function catches an assertion failure. It 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 HandleAssertFailure (const char* file, int line, const char* funcname, const char* expr)
-{
-#ifdef __unix__
-	AssertionFailureText = format ("%1:%2: %3: %4", file, line, funcname, expr);
-#else
-	DisplayBombBox (format (
-		"<p><b>File</b>: <tt>%1</tt><br />"
-		"<b>Line</b>: <tt>%2</tt><br />"
-		"<b>Function:</b> <tt>%3</tt></p>"
-		"<p>Assertion <b><tt>`%4'</tt></b> failed.</p>",
-		file, line, funcname, expr));
-#endif
-
-	abort();
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/crashCatcher.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,161 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu 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 <unistd.h>
+#include <signal.h>
+#include "crashCatcher.h"
+#include "dialogs.h"
+ 
+#ifdef __unix__
+# ifdef Q_OS_LINUX
+#  include <sys/prctl.h>
+# endif
+
+// Is the crash catcher active now?
+static bool IsCrashCatcherActive = false;
+
+// If an assertion failed, what was it?
+static QString AssertionFailureText;
+
+// List of signals to catch and crash on
+static QList<int> SignalsToCatch ({
+	SIGSEGV, // segmentation fault
+	SIGABRT, // abort() calls
+	SIGFPE, // floating point exceptions (e.g. division by zero)
+	SIGILL, // illegal instructions
+});
+
+// -------------------------------------------------------------------------------------------------
+//
+//	Removes the signal handler from SIGABRT and then aborts.
+//
+static void FinalAbort()
+{
+	struct sigaction sighandler;
+	sighandler.sa_handler = SIG_DFL;
+	sighandler.sa_flags = 0;
+	sigaction (SIGABRT, &sighandler, 0);
+	abort();
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+static void HandleCrash (int sig)
+{
+	printf ("!! Caught signal %d, launching gdb\n", sig);
+
+	if (IsCrashCatcherActive)
+	{
+		printf ("Caught signal while crash catcher is active! Execution cannot continue.\n");
+		FinalAbort();
+	}
+
+	pid_t const pid (getpid());
+	QProcess proc;
+	QTemporaryFile commandsFile;
+
+	IsCrashCatcherActive = true;
+
+	if (commandsFile.open())
+	{
+		commandsFile.write (format ("attach %1\n", pid).toLocal8Bit());
+		commandsFile.write (QString ("backtrace full\n").toLocal8Bit());
+		commandsFile.write (QString ("detach\n").toLocal8Bit());
+		commandsFile.write (QString ("quit").toLocal8Bit());
+		commandsFile.close();
+	}
+
+	proc.start ("gdb", {"-x", commandsFile.fileName()});
+
+	// Linux doesn't allow ptrace to be used on anything but direct child processes
+	// so we need to use prctl to register an exception to this to allow GDB attach to us.
+	// We need to do this now and no earlier because only now we actually know GDB's PID.
+#ifdef Q_OS_LINUX
+	prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0);
+#endif
+
+	proc.waitForFinished (1000);
+	QString output (proc.readAllStandardOutput());
+	QString err (proc.readAllStandardError());
+	QFile f (UNIXNAME "-crash.log");
+
+	if (f.open (QIODevice::WriteOnly))
+	{
+		fprint (f, format ("=== Program crashed with signal %1 ===\n\n%2"
+			"GDB stdout:\n%3\nGDB stderr:\n%4\n", sig,
+			(not AssertionFailureText.isEmpty()) ? AssertionFailureText + "\n\n" : "",
+			output, err));
+		f.close();
+	}
+
+	if (not AssertionFailureText.isEmpty())
+		printf ("Assertion failed: \"%s\".\n", qPrintable (AssertionFailureText));
+
+	printf ("Backtrace written to " UNIXNAME "-crash.log. Aborting.\n");
+	FinalAbort();
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+//	Initializes the crash catcher.
+//
+void InitCrashCatcher()
+{
+	struct sigaction sighandler;
+	sighandler.sa_handler = &HandleCrash;
+	sighandler.sa_flags = 0;
+	sigemptyset (&sighandler.sa_mask);
+
+	for (int sig : SignalsToCatch)
+	{
+		if (sigaction (sig, &sighandler, null) == -1)
+		{
+			fprint (stderr, "Couldn't set signal handler %1: %2", sig, strerror (errno));
+			SignalsToCatch.removeOne (sig);
+		}
+	}
+
+	print ("Crash catcher hooked to signals: %1\n", SignalsToCatch);
+}
+
+#endif // #ifdef __unix__
+
+// -------------------------------------------------------------------------------------------------
+//
+// This function catches an assertion failure. It 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 HandleAssertFailure (const char* file, int line, const char* funcname, const char* expr)
+{
+#ifdef __unix__
+	AssertionFailureText = format ("%1:%2: %3: %4", file, line, funcname, expr);
+#else
+	DisplayBombBox (format (
+		"<p><b>File</b>: <tt>%1</tt><br />"
+		"<b>Line</b>: <tt>%2</tt><br />"
+		"<b>Function:</b> <tt>%3</tt></p>"
+		"<p>Assertion <b><tt>`%4'</tt></b> failed.</p>",
+		file, line, funcname, expr));
+#endif
+
+	abort();
+}
\ No newline at end of file
--- a/src/dialogs.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,378 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDialog>
-#include <QLineEdit>
-#include <QSpinBox>
-#include <QDialogButtonBox>
-#include <QFileDialog>
-#include <QLabel>
-#include <QPushButton>
-#include <QBoxLayout>
-#include <QGridLayout>
-#include <QProgressBar>
-#include <QCheckBox>
-#include <QDesktopServices>
-#include <QMessageBox>
-#include <QUrl>
-#include "dialogs.h"
-#include "radioGroup.h"
-#include "mainWindow.h"
-#include "glRenderer.h"
-#include "documentation.h"
-#include "ldDocument.h"
-#include "dialogs.h"
-#include "ui_overlay.h"
-#include "ui_ldrawpath.h"
-#include "ui_openprogress.h"
-#include "ui_extprogpath.h"
-#include "ui_about.h"
-#include "ui_bombbox.h"
-
-extern const char* g_extProgPathFilter;
-EXTERN_CFGENTRY (String, LDrawPath)
-
-// =============================================================================
-// =============================================================================
-OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f)
-{
-	ui = new Ui_OverlayUI;
-	ui->setupUi (this);
-
-	m_cameraArgs =
-	{
-		{ ui->top,    ETopCamera },
-		{ ui->bottom, EBottomCamera },
-		{ ui->front,  EFrontCamera },
-		{ ui->back,   EBackCamera },
-		{ ui->left,   ELeftCamera },
-		{ ui->right,  ERightCamera }
-	};
-
-	ECamera cam = g_win->R()->camera();
-
-	if (cam == EFreeCamera)
-		cam = ETopCamera;
-
-	connect (ui->width, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged()));
-	connect (ui->height, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged()));
-	connect (ui->buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_help()));
-	connect (ui->fileSearchButton, SIGNAL (clicked (bool)), this, SLOT (slot_fpath()));
-
-	slot_dimensionsChanged();
-	fillDefaults (cam);
-}
-
-// =============================================================================
-// =============================================================================
-OverlayDialog::~OverlayDialog()
-{
-	delete ui;
-}
-
-// =============================================================================
-// =============================================================================
-void OverlayDialog::fillDefaults (int newcam)
-{
-	LDGLOverlay& info = g_win->R()->getOverlay (newcam);
-	RadioDefault<int> (newcam, m_cameraArgs);
-
-	if (info.img != null)
-	{
-		ui->filename->setText (info.fname);
-		ui->originX->setValue (info.ox);
-		ui->originY->setValue (info.oy);
-		ui->width->setValue (info.lw);
-		ui->height->setValue (info.lh);
-	}
-	else
-	{
-		ui->filename->setText ("");
-		ui->originX->setValue (0);
-		ui->originY->setValue (0);
-		ui->width->setValue (0.0f);
-		ui->height->setValue (0.0f);
-	}
-}
-
-// =============================================================================
-// =============================================================================
-QString OverlayDialog::fpath() const
-{
-	return ui->filename->text();
-}
-
-int OverlayDialog::ofsx() const
-{
-	return ui->originX->value();
-}
-
-int OverlayDialog::ofsy() const
-{
-	return ui->originY->value();
-}
-
-double OverlayDialog::lwidth() const
-{
-	return ui->width->value();
-}
-
-double OverlayDialog::lheight() const
-{
-	return ui->height->value();
-}
-
-int OverlayDialog::camera() const
-{
-	return RadioSwitch<int> (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) or (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 (cfg::LDrawPath);
-
-	if (validDefault)
-		slot_tryConfigure();
-}
-
-// =============================================================================
-// =============================================================================
-LDrawPathDialog::~LDrawPathDialog()
-{
-	delete ui;
-}
-
-QPushButton* LDrawPathDialog::okButton()
-{
-	return ui->buttonBox->button (QDialogButtonBox::Ok);
-}
-
-QPushButton* LDrawPathDialog::cancelButton()
-{
-	return ui->buttonBox->button (QDialogButtonBox::Cancel);
-}
-
-void LDrawPathDialog::setPath (QString path)
-{
-	ui->path->setText (path);
-}
-
-QString LDrawPathDialog::filename() const
-{
-	return ui->path->text();
-}
-
-// =============================================================================
-// =============================================================================
-void LDrawPathDialog::slot_findPath()
-{
-	QString newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path");
-
-	if (not newpath.isEmpty())
-	{
-		setPath (newpath);
-		slot_tryConfigure();
-	}
-}
-
-// =============================================================================
-// =============================================================================
-void LDrawPathDialog::slot_exit()
-{
-	Exit();
-}
-
-// =============================================================================
-// =============================================================================
-void LDrawPathDialog::slot_tryConfigure()
-{
-	if (not LDPaths::tryConfigure (filename()))
-	{
-		ui->status->setText (format ("<span style=\"color:#700; \">%1</span>", LDPaths::getError()));
-		okButton()->setEnabled (false);
-		return;
-	}
-
-	ui->status->setText ("<span style=\"color: #270; \">OK!</span>");
-	okButton()->setEnabled (true);
-}
-
-// =============================================================================
-// =============================================================================
-void LDrawPathDialog::slot_accept()
-{
-	Config::Save();
-	accept();
-}
-
-// =============================================================================
-// =============================================================================
-OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f)
-{
-	ui = new Ui_OpenProgressUI;
-	ui->setupUi (this);
-	ui->progressText->setText ("Parsing...");
-	setNumLines (0);
-	m_progress = 0;
-}
-
-// =============================================================================
-// =============================================================================
-OpenProgressDialog::~OpenProgressDialog()
-{
-	delete ui;
-}
-
-// =============================================================================
-// =============================================================================
-void OpenProgressDialog::setNumLines (int const& a)
-{
-	m_numLines = a;
-	ui->progressBar->setRange (0, numLines());
-	updateValues();
-}
-
-// =============================================================================
-// =============================================================================
-void OpenProgressDialog::updateValues()
-{
-	ui->progressText->setText (format ("Parsing... %1 / %2", progress(), numLines()));
-	ui->progressBar->setValue (progress());
-}
-
-// =============================================================================
-// =============================================================================
-void OpenProgressDialog::updateProgress (int progress)
-{
-	setProgress (progress);
-	updateValues();
-}
-
-// =============================================================================
-// =============================================================================
-ExtProgPathPrompt::ExtProgPathPrompt (QString progName, QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f),
-	ui (new Ui_ExtProgPath)
-{
-	ui->setupUi (this);
-	QString labelText = ui->m_label->text();
-	labelText.replace ("<PROGRAM>", progName);
-	ui->m_label->setText (labelText);
-	connect (ui->m_findPath, SIGNAL (clicked (bool)), this, SLOT (findPath()));
-}
-
-// =============================================================================
-// =============================================================================
-ExtProgPathPrompt::~ExtProgPathPrompt()
-{
-	delete ui;
-}
-
-// =============================================================================
-// =============================================================================
-void ExtProgPathPrompt::findPath()
-{
-	QString path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter);
-
-	if (not path.isEmpty())
-		ui->m_path->setText (path);
-}
-
-// =============================================================================
-// =============================================================================
-QString ExtProgPathPrompt::getPath() const
-{
-	return ui->m_path->text();
-}
-
-// =============================================================================
-// =============================================================================
-AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f)
-{
-	Ui::AboutUI ui;
-	ui.setupUi (this);
-	ui.versionInfo->setText (APPNAME " " + QString (FullVersionString()));
-
-	QPushButton* mailButton = new QPushButton;
-	mailButton->setText (tr ("Contact"));
-	mailButton->setIcon (GetIcon ("mail"));
-	ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole);
-	connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail()));
-
-	setWindowTitle (format (tr ("About %1"), APPNAME));
-}
-
-// =============================================================================
-// =============================================================================
-void AboutDialog::slot_mail()
-{
-	QDesktopServices::openUrl (QUrl ("mailto:Teemu Piippo <arezey@gmail.com>?subject=LDForge"));
-}
-
-// =============================================================================
-// =============================================================================
-void DisplayBombBox (const QString& message)
-{
-	QDialog dlg (g_win);
-	Ui_BombBox ui;
-
-	ui.setupUi (&dlg);
-	ui.m_text->setText (message);
-	ui.buttonBox->button (QDialogButtonBox::Close)->setText (QObject::tr ("Damn it"));
-	dlg.exec();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dialogs.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,378 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDialog>
+#include <QLineEdit>
+#include <QSpinBox>
+#include <QDialogButtonBox>
+#include <QFileDialog>
+#include <QLabel>
+#include <QPushButton>
+#include <QBoxLayout>
+#include <QGridLayout>
+#include <QProgressBar>
+#include <QCheckBox>
+#include <QDesktopServices>
+#include <QMessageBox>
+#include <QUrl>
+#include "dialogs.h"
+#include "radioGroup.h"
+#include "mainWindow.h"
+#include "glRenderer.h"
+#include "documentation.h"
+#include "ldDocument.h"
+#include "dialogs.h"
+#include "ui_overlay.h"
+#include "ui_ldrawpath.h"
+#include "ui_openprogress.h"
+#include "ui_extprogpath.h"
+#include "ui_about.h"
+#include "ui_bombbox.h"
+
+extern const char* g_extProgPathFilter;
+EXTERN_CFGENTRY (String, LDrawPath)
+
+// =============================================================================
+// =============================================================================
+OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f)
+{
+	ui = new Ui_OverlayUI;
+	ui->setupUi (this);
+
+	m_cameraArgs =
+	{
+		{ ui->top,    ETopCamera },
+		{ ui->bottom, EBottomCamera },
+		{ ui->front,  EFrontCamera },
+		{ ui->back,   EBackCamera },
+		{ ui->left,   ELeftCamera },
+		{ ui->right,  ERightCamera }
+	};
+
+	ECamera cam = g_win->R()->camera();
+
+	if (cam == EFreeCamera)
+		cam = ETopCamera;
+
+	connect (ui->width, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged()));
+	connect (ui->height, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged()));
+	connect (ui->buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_help()));
+	connect (ui->fileSearchButton, SIGNAL (clicked (bool)), this, SLOT (slot_fpath()));
+
+	slot_dimensionsChanged();
+	fillDefaults (cam);
+}
+
+// =============================================================================
+// =============================================================================
+OverlayDialog::~OverlayDialog()
+{
+	delete ui;
+}
+
+// =============================================================================
+// =============================================================================
+void OverlayDialog::fillDefaults (int newcam)
+{
+	LDGLOverlay& info = g_win->R()->getOverlay (newcam);
+	RadioDefault<int> (newcam, m_cameraArgs);
+
+	if (info.img != null)
+	{
+		ui->filename->setText (info.fname);
+		ui->originX->setValue (info.ox);
+		ui->originY->setValue (info.oy);
+		ui->width->setValue (info.lw);
+		ui->height->setValue (info.lh);
+	}
+	else
+	{
+		ui->filename->setText ("");
+		ui->originX->setValue (0);
+		ui->originY->setValue (0);
+		ui->width->setValue (0.0f);
+		ui->height->setValue (0.0f);
+	}
+}
+
+// =============================================================================
+// =============================================================================
+QString OverlayDialog::fpath() const
+{
+	return ui->filename->text();
+}
+
+int OverlayDialog::ofsx() const
+{
+	return ui->originX->value();
+}
+
+int OverlayDialog::ofsy() const
+{
+	return ui->originY->value();
+}
+
+double OverlayDialog::lwidth() const
+{
+	return ui->width->value();
+}
+
+double OverlayDialog::lheight() const
+{
+	return ui->height->value();
+}
+
+int OverlayDialog::camera() const
+{
+	return RadioSwitch<int> (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) or (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 (cfg::LDrawPath);
+
+	if (validDefault)
+		slot_tryConfigure();
+}
+
+// =============================================================================
+// =============================================================================
+LDrawPathDialog::~LDrawPathDialog()
+{
+	delete ui;
+}
+
+QPushButton* LDrawPathDialog::okButton()
+{
+	return ui->buttonBox->button (QDialogButtonBox::Ok);
+}
+
+QPushButton* LDrawPathDialog::cancelButton()
+{
+	return ui->buttonBox->button (QDialogButtonBox::Cancel);
+}
+
+void LDrawPathDialog::setPath (QString path)
+{
+	ui->path->setText (path);
+}
+
+QString LDrawPathDialog::filename() const
+{
+	return ui->path->text();
+}
+
+// =============================================================================
+// =============================================================================
+void LDrawPathDialog::slot_findPath()
+{
+	QString newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path");
+
+	if (not newpath.isEmpty())
+	{
+		setPath (newpath);
+		slot_tryConfigure();
+	}
+}
+
+// =============================================================================
+// =============================================================================
+void LDrawPathDialog::slot_exit()
+{
+	Exit();
+}
+
+// =============================================================================
+// =============================================================================
+void LDrawPathDialog::slot_tryConfigure()
+{
+	if (not LDPaths::tryConfigure (filename()))
+	{
+		ui->status->setText (format ("<span style=\"color:#700; \">%1</span>", LDPaths::getError()));
+		okButton()->setEnabled (false);
+		return;
+	}
+
+	ui->status->setText ("<span style=\"color: #270; \">OK!</span>");
+	okButton()->setEnabled (true);
+}
+
+// =============================================================================
+// =============================================================================
+void LDrawPathDialog::slot_accept()
+{
+	Config::Save();
+	accept();
+}
+
+// =============================================================================
+// =============================================================================
+OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f)
+{
+	ui = new Ui_OpenProgressUI;
+	ui->setupUi (this);
+	ui->progressText->setText ("Parsing...");
+	setNumLines (0);
+	m_progress = 0;
+}
+
+// =============================================================================
+// =============================================================================
+OpenProgressDialog::~OpenProgressDialog()
+{
+	delete ui;
+}
+
+// =============================================================================
+// =============================================================================
+void OpenProgressDialog::setNumLines (int const& a)
+{
+	m_numLines = a;
+	ui->progressBar->setRange (0, numLines());
+	updateValues();
+}
+
+// =============================================================================
+// =============================================================================
+void OpenProgressDialog::updateValues()
+{
+	ui->progressText->setText (format ("Parsing... %1 / %2", progress(), numLines()));
+	ui->progressBar->setValue (progress());
+}
+
+// =============================================================================
+// =============================================================================
+void OpenProgressDialog::updateProgress (int progress)
+{
+	setProgress (progress);
+	updateValues();
+}
+
+// =============================================================================
+// =============================================================================
+ExtProgPathPrompt::ExtProgPathPrompt (QString progName, QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f),
+	ui (new Ui_ExtProgPath)
+{
+	ui->setupUi (this);
+	QString labelText = ui->m_label->text();
+	labelText.replace ("<PROGRAM>", progName);
+	ui->m_label->setText (labelText);
+	connect (ui->m_findPath, SIGNAL (clicked (bool)), this, SLOT (findPath()));
+}
+
+// =============================================================================
+// =============================================================================
+ExtProgPathPrompt::~ExtProgPathPrompt()
+{
+	delete ui;
+}
+
+// =============================================================================
+// =============================================================================
+void ExtProgPathPrompt::findPath()
+{
+	QString path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter);
+
+	if (not path.isEmpty())
+		ui->m_path->setText (path);
+}
+
+// =============================================================================
+// =============================================================================
+QString ExtProgPathPrompt::getPath() const
+{
+	return ui->m_path->text();
+}
+
+// =============================================================================
+// =============================================================================
+AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) :
+	QDialog (parent, f)
+{
+	Ui::AboutUI ui;
+	ui.setupUi (this);
+	ui.versionInfo->setText (APPNAME " " + QString (FullVersionString()));
+
+	QPushButton* mailButton = new QPushButton;
+	mailButton->setText (tr ("Contact"));
+	mailButton->setIcon (GetIcon ("mail"));
+	ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole);
+	connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail()));
+
+	setWindowTitle (format (tr ("About %1"), APPNAME));
+}
+
+// =============================================================================
+// =============================================================================
+void AboutDialog::slot_mail()
+{
+	QDesktopServices::openUrl (QUrl ("mailto:Teemu Piippo <arezey@gmail.com>?subject=LDForge"));
+}
+
+// =============================================================================
+// =============================================================================
+void DisplayBombBox (const QString& message)
+{
+	QDialog dlg (g_win);
+	Ui_BombBox ui;
+
+	ui.setupUi (&dlg);
+	ui.m_text->setText (message);
+	ui.buttonBox->button (QDialogButtonBox::Close)->setText (QObject::tr ("Damn it"));
+	dlg.exec();
+}
--- a/src/documentation.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDialog>
-#include <QTextEdit>
-#include <QDialogButtonBox>
-#include <QBoxLayout>
-#include "main.h"
-#include "basics.h"
-
-// =============================================================================
-// =============================================================================
-class DocumentViewer : public QDialog
-{
-	public:
-		explicit DocumentViewer (QWidget* parent = null, Qt::WindowFlags f = 0) : QDialog (parent, f)
-		{
-			te_text = new QTextEdit (this);
-			te_text->setMinimumSize (QSize (400, 300));
-			te_text->setReadOnly (true);
-
-			QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Close);
-			QVBoxLayout* layout = new QVBoxLayout (this);
-			layout->addWidget (te_text);
-			layout->addWidget (bbx_buttons);
-
-			connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject()));
-		}
-
-		void setText (const char* text)
-		{
-			te_text->setText (text);
-		}
-
-	private:
-		QTextEdit* te_text;
-};
-
-const char* g_docs_overlays =
-	"<h1>Overlay images</h1><br />"
-	"<p>" APPNAME " supports drawing transparent images over the part model. This "
-	"can be used to have, for instance, a photo of the part overlaid on top of the "
-	"model and use it for drawing curves somewhat accurately.</p>"
-	"<p>For this purpose, a specific photo has to be taken of the part; it should "
-	"represent the part as true as possible to the actual camera used for editing. "
-	"The image should be taken from straight above the part, at as an orthogonal "
-	"angle as possible. It is recommended to take a lot of pictures this way and "
-	"select the best candidate.</p>"
-	"<p>The image should then be cropped with the knowledge of the image's LDU "
-	"dimensions in mind. The offset should then be identified in the image in pixels.</p>"
-	"<p>Finally, use the \"Set Overlay Image\" dialog and fill in the details. The "
-	"overlay image should then be ready for use.";
-
-// =============================================================================
-// =============================================================================
-void showDocumentation (const char* text)
-{
-	DocumentViewer dlg;
-	dlg.setText (text);
-	dlg.exec();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/documentation.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,76 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDialog>
+#include <QTextEdit>
+#include <QDialogButtonBox>
+#include <QBoxLayout>
+#include "main.h"
+#include "basics.h"
+
+// =============================================================================
+// =============================================================================
+class DocumentViewer : public QDialog
+{
+	public:
+		explicit DocumentViewer (QWidget* parent = null, Qt::WindowFlags f = 0) : QDialog (parent, f)
+		{
+			te_text = new QTextEdit (this);
+			te_text->setMinimumSize (QSize (400, 300));
+			te_text->setReadOnly (true);
+
+			QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Close);
+			QVBoxLayout* layout = new QVBoxLayout (this);
+			layout->addWidget (te_text);
+			layout->addWidget (bbx_buttons);
+
+			connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject()));
+		}
+
+		void setText (const char* text)
+		{
+			te_text->setText (text);
+		}
+
+	private:
+		QTextEdit* te_text;
+};
+
+const char* g_docs_overlays =
+	"<h1>Overlay images</h1><br />"
+	"<p>" APPNAME " supports drawing transparent images over the part model. This "
+	"can be used to have, for instance, a photo of the part overlaid on top of the "
+	"model and use it for drawing curves somewhat accurately.</p>"
+	"<p>For this purpose, a specific photo has to be taken of the part; it should "
+	"represent the part as true as possible to the actual camera used for editing. "
+	"The image should be taken from straight above the part, at as an orthogonal "
+	"angle as possible. It is recommended to take a lot of pictures this way and "
+	"select the best candidate.</p>"
+	"<p>The image should then be cropped with the knowledge of the image's LDU "
+	"dimensions in mind. The offset should then be identified in the image in pixels.</p>"
+	"<p>Finally, use the \"Set Overlay Image\" dialog and fill in the details. The "
+	"overlay image should then be ready for use.";
+
+// =============================================================================
+// =============================================================================
+void showDocumentation (const char* text)
+{
+	DocumentViewer dlg;
+	dlg.setText (text);
+	dlg.exec();
+}
--- a/src/editHistory.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,203 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "editHistory.h"
-#include "ldObject.h"
-#include "ldDocument.h"
-#include "miscallenous.h"
-#include "mainWindow.h"
-#include "glRenderer.h"
-
-// =============================================================================
-//
-History::History() :
-	m_position (-1),
-	m_isIgnoring (false) {}
-
-// =============================================================================
-//
-void History::undo()
-{
-	if (m_changesets.isEmpty() or position() == -1)
-		return;
-
-	// Don't take the changes done here as actual edits to the document
-	setIgnoring (true);
-
-	const Changeset& set = getChangeset (position());
-
-	// Iterate the list in reverse and undo all actions
-	for (int i = set.size() - 1; i >= 0; --i)
-	{
-		AbstractHistoryEntry* change = set[i];
-		change->undo();
-	}
-
-	m_position--;
-	g_win->refresh();
-	g_win->updateActions();
-	dprint ("Position is now %1", position());
-	setIgnoring (false);
-}
-
-// =============================================================================
-//
-void History::redo()
-{
-	if (position() == m_changesets.size())
-		return;
-
-	setIgnoring (true);
-	const Changeset& set = getChangeset (position() + 1);
-
-	// Redo things - in the order as they were done in the first place
-	for (const AbstractHistoryEntry* change : set)
-		change->redo();
-
-	setPosition (position() + 1);
-	g_win->refresh();
-	g_win->updateActions();
-	dprint ("Position is now %1", position());
-	setIgnoring (false);
-}
-
-// =============================================================================
-//
-void History::clear()
-{
-	for (Changeset set : m_changesets)
-		for (AbstractHistoryEntry* change : set)
-			delete change;
-
-	m_changesets.clear();
-	dprint ("History: cleared");
-}
-
-// =============================================================================
-//
-void History::addStep()
-{
-	if (m_currentChangeset.isEmpty())
-		return;
-
-	while (position() < getSize() - 1)
-	{
-		Changeset last = m_changesets.last();
-
-		for (AbstractHistoryEntry* entry : last)
-			delete entry;
-
-		m_changesets.removeLast();
-	}
-
-//	dprint ("History: step added (%1 changes)", m_currentChangeset.size());
-	m_changesets << m_currentChangeset;
-	m_currentChangeset.clear();
-	setPosition (position() + 1);
-	g_win->updateActions();
-}
-
-// =============================================================================
-//
-void History::add (AbstractHistoryEntry* entry)
-{
-	if (isIgnoring())
-	{
-		delete entry;
-		return;
-	}
-
-	entry->setParent (this);
-	m_currentChangeset << entry;
-//	dprint ("History: added entry of type %1", entry->getTypeName());
-}
-
-// =============================================================================
-//
-void AddHistory::undo() const
-{
-	LDObjectPtr obj = parent()->document().toStrongRef()->getObject (index());
-	obj->destroy();
-}
-
-// =============================================================================
-//
-void AddHistory::redo() const
-{
-	LDObjectPtr obj = ParseLine (code());
-	parent()->document().toStrongRef()->insertObj (index(), obj);
-	g_win->R()->compileObject (obj);
-}
-
-// =============================================================================
-//
-DelHistory::DelHistory (int idx, LDObjectPtr obj) :
-	m_index (idx),
-	m_code (obj->asText()) {}
-
-// =============================================================================
-// heh
-//
-void DelHistory::undo() const
-{
-	LDObjectPtr obj = ParseLine (code());
-	parent()->document().toStrongRef()->insertObj (index(), obj);
-	g_win->R()->compileObject (obj);
-}
-
-// =============================================================================
-//
-void DelHistory::redo() const
-{
-	LDObjectPtr obj = parent()->document().toStrongRef()->getObject (index());
-	obj->destroy();
-}
-
-// =============================================================================
-//
-void EditHistory::undo() const
-{
-	LDObjectPtr obj = CurrentDocument()->getObject (index());
-	LDObjectPtr newobj = ParseLine (oldCode());
-	obj->replace (newobj);
-	g_win->R()->compileObject (newobj);
-}
-
-// =============================================================================
-//
-void EditHistory::redo() const
-{
-	LDObjectPtr obj = CurrentDocument()->getObject (index());
-	LDObjectPtr newobj = ParseLine (newCode());
-	obj->replace (newobj);
-	g_win->R()->compileObject (newobj);
-}
-
-// =============================================================================
-//
-void SwapHistory::undo() const
-{
-	LDObject::fromID (a)->swap (LDObject::fromID (b));
-}
-
-// =============================================================================
-//
-void SwapHistory::redo() const
-{
-	undo();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editHistory.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,203 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "editHistory.h"
+#include "ldObject.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "glRenderer.h"
+
+// =============================================================================
+//
+History::History() :
+	m_position (-1),
+	m_isIgnoring (false) {}
+
+// =============================================================================
+//
+void History::undo()
+{
+	if (m_changesets.isEmpty() or position() == -1)
+		return;
+
+	// Don't take the changes done here as actual edits to the document
+	setIgnoring (true);
+
+	const Changeset& set = getChangeset (position());
+
+	// Iterate the list in reverse and undo all actions
+	for (int i = set.size() - 1; i >= 0; --i)
+	{
+		AbstractHistoryEntry* change = set[i];
+		change->undo();
+	}
+
+	m_position--;
+	g_win->refresh();
+	g_win->updateActions();
+	dprint ("Position is now %1", position());
+	setIgnoring (false);
+}
+
+// =============================================================================
+//
+void History::redo()
+{
+	if (position() == m_changesets.size())
+		return;
+
+	setIgnoring (true);
+	const Changeset& set = getChangeset (position() + 1);
+
+	// Redo things - in the order as they were done in the first place
+	for (const AbstractHistoryEntry* change : set)
+		change->redo();
+
+	setPosition (position() + 1);
+	g_win->refresh();
+	g_win->updateActions();
+	dprint ("Position is now %1", position());
+	setIgnoring (false);
+}
+
+// =============================================================================
+//
+void History::clear()
+{
+	for (Changeset set : m_changesets)
+		for (AbstractHistoryEntry* change : set)
+			delete change;
+
+	m_changesets.clear();
+	dprint ("History: cleared");
+}
+
+// =============================================================================
+//
+void History::addStep()
+{
+	if (m_currentChangeset.isEmpty())
+		return;
+
+	while (position() < getSize() - 1)
+	{
+		Changeset last = m_changesets.last();
+
+		for (AbstractHistoryEntry* entry : last)
+			delete entry;
+
+		m_changesets.removeLast();
+	}
+
+//	dprint ("History: step added (%1 changes)", m_currentChangeset.size());
+	m_changesets << m_currentChangeset;
+	m_currentChangeset.clear();
+	setPosition (position() + 1);
+	g_win->updateActions();
+}
+
+// =============================================================================
+//
+void History::add (AbstractHistoryEntry* entry)
+{
+	if (isIgnoring())
+	{
+		delete entry;
+		return;
+	}
+
+	entry->setParent (this);
+	m_currentChangeset << entry;
+//	dprint ("History: added entry of type %1", entry->getTypeName());
+}
+
+// =============================================================================
+//
+void AddHistory::undo() const
+{
+	LDObjectPtr obj = parent()->document().toStrongRef()->getObject (index());
+	obj->destroy();
+}
+
+// =============================================================================
+//
+void AddHistory::redo() const
+{
+	LDObjectPtr obj = ParseLine (code());
+	parent()->document().toStrongRef()->insertObj (index(), obj);
+	g_win->R()->compileObject (obj);
+}
+
+// =============================================================================
+//
+DelHistory::DelHistory (int idx, LDObjectPtr obj) :
+	m_index (idx),
+	m_code (obj->asText()) {}
+
+// =============================================================================
+// heh
+//
+void DelHistory::undo() const
+{
+	LDObjectPtr obj = ParseLine (code());
+	parent()->document().toStrongRef()->insertObj (index(), obj);
+	g_win->R()->compileObject (obj);
+}
+
+// =============================================================================
+//
+void DelHistory::redo() const
+{
+	LDObjectPtr obj = parent()->document().toStrongRef()->getObject (index());
+	obj->destroy();
+}
+
+// =============================================================================
+//
+void EditHistory::undo() const
+{
+	LDObjectPtr obj = CurrentDocument()->getObject (index());
+	LDObjectPtr newobj = ParseLine (oldCode());
+	obj->replace (newobj);
+	g_win->R()->compileObject (newobj);
+}
+
+// =============================================================================
+//
+void EditHistory::redo() const
+{
+	LDObjectPtr obj = CurrentDocument()->getObject (index());
+	LDObjectPtr newobj = ParseLine (newCode());
+	obj->replace (newobj);
+	g_win->R()->compileObject (newobj);
+}
+
+// =============================================================================
+//
+void SwapHistory::undo() const
+{
+	LDObject::fromID (a)->swap (LDObject::fromID (b));
+}
+
+// =============================================================================
+//
+void SwapHistory::redo() const
+{
+	undo();
+}
--- a/src/extPrograms.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,721 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu 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 <QFileInfo>
-#include "main.h"
-#include "configuration.h"
-#include "miscallenous.h"
-#include "mainWindow.h"
-#include "ldDocument.h"
-#include "radioGroup.h"
-#include "editHistory.h"
-#include "ui_ytruder.h"
-#include "ui_intersector.h"
-#include "ui_rectifier.h"
-#include "ui_coverer.h"
-#include "ui_isecalc.h"
-#include "ui_edger2.h"
-#include "dialogs.h"
-
-enum extprog
-{
-	Isecalc,
-	Intersector,
-	Coverer,
-	Ytruder,
-	Rectifier,
-	Edger2,
-};
-
-// =============================================================================
-//
-CFGENTRY (String, IsecalcPath, "")
-CFGENTRY (String, IntersectorPath, "")
-CFGENTRY (String, CovererPath, "")
-CFGENTRY (String, YtruderPath, "")
-CFGENTRY (String, RectifierPath, "")
-CFGENTRY (String, Edger2Path, "")
-
-QString* const g_extProgPaths[] =
-{
-	&cfg::IsecalcPath,
-	&cfg::IntersectorPath,
-	&cfg::CovererPath,
-	&cfg::YtruderPath,
-	&cfg::RectifierPath,
-	&cfg::Edger2Path,
-};
-
-CFGENTRY (Bool, IsecalcUsesWine, false)
-CFGENTRY (Bool, IntersectorUsesWine, false)
-CFGENTRY (Bool, CovererUsesWine, false)
-CFGENTRY (Bool, YtruderUsesWine, false)
-CFGENTRY (Bool, RectifierUsesWine, false)
-CFGENTRY (Bool, Edger2UsesWine, false)
-
-bool* const g_extProgWine[] =
-{
-	&cfg::IsecalcUsesWine,
-	&cfg::IntersectorUsesWine,
-	&cfg::CovererUsesWine,
-	&cfg::YtruderUsesWine,
-	&cfg::RectifierUsesWine,
-	&cfg::Edger2UsesWine,
-};
-
-const char* g_extProgNames[] =
-{
-	"Isecalc",
-	"Intersector",
-	"Coverer",
-	"Ytruder",
-	"Rectifier",
-	"Edger2"
-};
-
-// =============================================================================
-//
-static bool MakeTempFile (QTemporaryFile& tmp, QString& fname)
-{
-	if (not tmp.open())
-		return false;
-
-	fname = tmp.fileName();
-	tmp.close();
-	return true;
-}
-
-// =============================================================================
-//
-static bool CheckExtProgramPath (const extprog prog)
-{
-	QString& path = *g_extProgPaths[prog];
-
-	if (not path.isEmpty())
-		return true;
-
-	ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]);
-
-	if (dlg->exec() and not dlg->getPath().isEmpty())
-	{
-		path = dlg->getPath();
-		return true;
-	}
-
-	return false;
-}
-
-// =============================================================================
-//
-static QString ProcessExtProgError (extprog prog, QProcess& proc)
-{
-	switch (proc.error())
-	{
-		case QProcess::FailedToStart:
-		{
-			QString wineblurb;
-
-#ifndef _WIN32
-			if (*g_extProgWine[prog])
-				wineblurb = "make sure Wine is installed and ";
-#else
-			(void) prog;
-#endif
-
-			return format ("Program failed to start, %1check your permissions", wineblurb);
-		} break;
-
-		case QProcess::Crashed:
-			return "Crashed.";
-
-		case QProcess::WriteError:
-		case QProcess::ReadError:
-			return "I/O error.";
-
-		case QProcess::UnknownError:
-			return "Unknown error";
-
-		case QProcess::Timedout:
-			return format ("Timed out (30 seconds)");
-	}
-
-	return "";
-}
-
-// =============================================================================
-//
-static void WriteObjects (const LDObjectList& objects, QFile& f)
-{
-	for (LDObjectPtr obj : objects)
-	{
-		if (obj->type() == OBJ_Subfile)
-		{
-			LDSubfilePtr ref = obj.staticCast<LDSubfile>();
-			LDObjectList objs = ref->inlineContents (true, false);
-
-			WriteObjects (objs, f);
-
-			for (LDObjectPtr obj : objs)
-				obj->destroy();
-		}
-		else
-			f.write ((obj->asText() + "\r\n").toUtf8());
-	}
-}
-
-// =============================================================================
-//
-static void WriteObjects (const LDObjectList& objects, QString fname)
-{
-	// Write the input file
-	QFile f (fname);
-
-	if (not f.open (QIODevice::WriteOnly | QIODevice::Text))
-	{
-		Critical (format ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString()));
-		return;
-	}
-
-	WriteObjects (objects, f);
-	f.close();
-
-#ifdef DEBUG
-	QFile::copy (fname, "debug_lastInput");
-#endif
-}
-
-// =============================================================================
-//
-void WriteSelection (QString fname)
-{
-	WriteObjects (Selection(), fname);
-}
-
-// =============================================================================
-//
-void WriteColorGroup (LDColor color, QString fname)
-{
-	LDObjectList objects;
-
-	for (LDObjectPtr obj : CurrentDocument()->objects())
-	{
-		if (not obj->isColored() or obj->color() != color)
-			continue;
-
-		objects << obj;
-	}
-
-	WriteObjects (objects, fname);
-}
-
-// =============================================================================
-//
-bool RunExtProgram (extprog prog, QString path, QString argvstr)
-{
-	QTemporaryFile input;
-	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
-
-#ifndef _WIN32
-	if (*g_extProgWine[prog])
-	{
-		argv.insert (0, path);
-		path = "wine";
-	}
-#endif // _WIN32
-
-	print ("Running command: %1 %2\n", path, argv.join (" "));
-
-	if (not input.open())
-		return false;
-
-	QProcess proc;
-
-	// Begin!
-	proc.setStandardInputFile (input.fileName());
-	proc.start (path, argv);
-
-	if (not proc.waitForStarted())
-	{
-		Critical (format ("Couldn't start %1: %2\n", g_extProgNames[prog], ProcessExtProgError (prog, proc)));
-		return false;
-	}
-
-	// Write an enter, the utility tools all expect one
-	input.write ("\n");
-
-	// Wait while it runs
-	proc.waitForFinished();
-
-	QString err = "";
-
-	if (proc.exitStatus() != QProcess::NormalExit)
-		err = ProcessExtProgError (prog, proc);
-
-	// Check the return code
-	if (proc.exitCode() != 0)
-		err = format ("Program exited abnormally (return code %1).",  proc.exitCode());
-
-	if (not err.isEmpty())
-	{
-		Critical (format ("%1 failed: %2\n", g_extProgNames[prog], err));
-		QString filename ("externalProgramOutput.txt");
-		QFile file (filename);
-
-		if (file.open (QIODevice::WriteOnly | QIODevice::Text))
-		{
-			file.write (proc.readAllStandardOutput());
-			file.write (proc.readAllStandardError());
-			print ("Wrote output and error logs to %1", QFileInfo (file).absoluteFilePath());
-		}
-		else
-		{
-			print ("Couldn't open %1 for writing: %2",
-				QFileInfo (filename).absoluteFilePath(), file.errorString());
-		}
-
-		return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-static void InsertOutput (QString fname, bool replace, QList<LDColor> colorsToReplace)
-{
-#ifdef DEBUG
-	QFile::copy (fname, "./debug_lastOutput");
-#endif // RELEASE
-
-	// Read the output file
-	QFile f (fname);
-
-	if (not f.open (QIODevice::ReadOnly))
-	{
-		Critical (format ("Couldn't open temporary file %1 for reading.\n", fname));
-		return;
-	}
-
-	LDObjectList objs = LoadFileContents (&f, null);
-
-	// If we replace the objects, delete the selection now.
-	if (replace)
-		g_win->deleteSelection();
-
-	for (LDColor color : colorsToReplace)
-		g_win->deleteByColor (color);
-
-	// Insert the new objects
-	CurrentDocument()->clearSelection();
-
-	for (LDObjectPtr obj : objs)
-	{
-		if (not obj->isScemantic())
-		{
-			obj->destroy();
-			continue;
-		}
-
-		CurrentDocument()->addObject (obj);
-		obj->select();
-	}
-
-	g_win->doFullRefresh();
-}
-
-// =============================================================================
-// Interface for Ytruder
-// =============================================================================
-void MainWindow::actionYtruder()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Ytruder))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::YtruderUI ui;
-	ui.setupUi (dlg);
-
-	if (not dlg->exec())
-		return;
-
-	// Read the user's choices
-	const enum { Distance, Symmetry, Projection, Radial } mode =
-		ui.mode_distance->isChecked()   ? Distance :
-		ui.mode_symmetry->isChecked()   ? Symmetry :
-		ui.mode_projection->isChecked() ? Projection : Radial;
-
-	const Axis axis =
-		ui.axis_x->isChecked() ? X :
-		ui.axis_y->isChecked() ? Y : Z;
-
-	const double depth = ui.planeDepth->value(),
-				 condAngle = ui.condAngle->value();
-
-	QTemporaryFile indat, outdat;
-	QString inDATName, outDATName;
-
-	// Make temp files for the input and output files
-	if (not MakeTempFile (indat, inDATName) or not MakeTempFile (outdat, outDATName))
-		return;
-
-	// Compose the command-line arguments
-	QString argv = Join (
-	{
-		(axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
-		(mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
-		depth,
-		"-a",
-		condAngle,
-		inDATName,
-		outDATName
-	});
-
-	WriteSelection (inDATName);
-
-	if (not RunExtProgram (Ytruder, cfg::YtruderPath, argv))
-		return;
-
-	InsertOutput (outDATName, false, {});
-}
-
-// =============================================================================
-// Rectifier interface
-// =============================================================================
-void MainWindow::actionRectifier()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Rectifier))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::RectifierUI ui;
-	ui.setupUi (dlg);
-
-	if (not dlg->exec())
-		return;
-
-	QTemporaryFile indat, outdat;
-	QString inDATName, outDATName;
-
-	// Make temp files for the input and output files
-	if (not MakeTempFile (indat, inDATName) or not MakeTempFile (outdat, outDATName))
-		return;
-
-	// Compose arguments
-	QString argv = Join (
-	{
-		(not ui.cb_condense->isChecked()) ? "-q" : "",
-		(not 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 (not RunExtProgram (Rectifier, cfg::RectifierPath, argv))
-		return;
-
-	InsertOutput (outDATName, true, {});
-}
-
-// =============================================================================
-// Intersector interface
-// =============================================================================
-void MainWindow::actionIntersector()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (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.");
-
-	LDColor inCol, cutCol;
-	const bool repeatInverse = ui.cb_repeat->isChecked();
-
-	forever
-	{
-		if (not dlg->exec())
-			return;
-
-		inCol = LDColor::fromIndex (ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt());
-		cutCol = LDColor::fromIndex (ui.cmb_cutcol->itemData (ui.cmb_cutcol->currentIndex()).toInt());
-
-		if (inCol == cutCol)
-		{
-			Critical ("Cannot use the same color group for both input and cutter!");
-			continue;
-		}
-
-		break;
-	}
-
-	// Five temporary files!
-	// indat = input group file
-	// cutdat = cutter group file
-	// outdat = primary output
-	// outdat2 = inverse output
-	// edgesdat = edges output (isecalc)
-	QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat;
-	QString inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
-
-	if (not MakeTempFile (indat, inDATName) or
-		not MakeTempFile (cutdat, cutDATName) or
-		not MakeTempFile (outdat, outDATName) or
-		not MakeTempFile (outdat2, outDAT2Name) or
-		not MakeTempFile (edgesdat, edgesDATName))
-	{
-		return;
-	}
-
-	QString parms = Join (
-	{
-		(ui.cb_colorize->isChecked()) ? "-c" : "",
-		(ui.cb_nocondense->isChecked()) ? "-t" : "",
-		"-s",
-		ui.dsb_prescale->value()
-	});
-
-	QString argv_normal = Join (
-	{
-		parms,
-		inDATName,
-		cutDATName,
-		outDATName
-	});
-
-	QString argv_inverse = Join (
-	{
-		parms,
-		cutDATName,
-		inDATName,
-		outDAT2Name
-	});
-
-	WriteColorGroup (inCol, inDATName);
-	WriteColorGroup (cutCol, cutDATName);
-
-	if (not RunExtProgram (Intersector, cfg::IntersectorPath, argv_normal))
-		return;
-
-	InsertOutput (outDATName, false, {inCol});
-
-	if (repeatInverse and RunExtProgram (Intersector, cfg::IntersectorPath, argv_inverse))
-		InsertOutput (outDAT2Name, false, {cutCol});
-
-	if (ui.cb_edges->isChecked() and CheckExtProgramPath (Isecalc) and
-		RunExtProgram (Isecalc, cfg::IsecalcPath, Join ({inDATName, cutDATName, edgesDATName})))
-	{
-		InsertOutput (edgesDATName, false, {});
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::actionCoverer()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Coverer))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::CovererUI ui;
-	ui.setupUi (dlg);
-	MakeColorComboBox (ui.cmb_col1);
-	MakeColorComboBox (ui.cmb_col2);
-
-	LDColor in1Col, in2Col;
-
-	forever
-	{
-		if (not dlg->exec())
-			return;
-
-		in1Col = LDColor::fromIndex (ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt());
-		in2Col = LDColor::fromIndex (ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt());
-
-		if (in1Col == in2Col)
-		{
-			Critical ("Cannot use the same color group for both inputs!");
-			continue;
-		}
-
-		break;
-	}
-
-	QTemporaryFile in1dat, in2dat, outdat;
-	QString in1DATName, in2DATName, outDATName;
-
-	if (not MakeTempFile (in1dat, in1DATName) or
-		not MakeTempFile (in2dat, in2DATName) or
-		not MakeTempFile (outdat, outDATName))
-	{
-		return;
-	}
-
-	QString argv = Join (
-	{
-		(ui.cb_oldsweep->isChecked() ? "-s" : ""),
-		(ui.cb_reverse->isChecked() ? "-r" : ""),
-		(ui.dsb_segsplit->value() != 0 ? format ("-l %1", ui.dsb_segsplit->value()) : ""),
-		(ui.sb_bias->value() != 0 ? format ("-s %1", ui.sb_bias->value()) : ""),
-		in1DATName,
-		in2DATName,
-		outDATName
-	});
-
-	WriteColorGroup (in1Col, in1DATName);
-	WriteColorGroup (in2Col, in2DATName);
-
-	if (not RunExtProgram (Coverer, cfg::CovererPath, argv))
-		return;
-
-	InsertOutput (outDATName, false, {});
-}
-
-// =============================================================================
-//
-void MainWindow::actionIsecalc()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Isecalc))
-		return;
-
-	Ui::IsecalcUI ui;
-	QDialog* dlg = new QDialog;
-	ui.setupUi (dlg);
-
-	MakeColorComboBox (ui.cmb_col1);
-	MakeColorComboBox (ui.cmb_col2);
-
-	LDColor in1Col, in2Col;
-
-	// Run the dialog and validate input
-	forever
-	{
-		if (not dlg->exec())
-			return;
-
-		in1Col = LDColor::fromIndex (ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt());
-		in2Col = LDColor::fromIndex (ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt());
-
-		if (in1Col == in2Col)
-		{
-			Critical ("Cannot use the same color group for both input and cutter!");
-			continue;
-		}
-
-		break;
-	}
-
-	QTemporaryFile in1dat, in2dat, outdat;
-	QString in1DATName, in2DATName, outDATName;
-
-	if (not MakeTempFile (in1dat, in1DATName) or
-		not MakeTempFile (in2dat, in2DATName) or
-		not MakeTempFile (outdat, outDATName))
-	{
-		return;
-	}
-
-	QString argv = Join (
-	{
-		in1DATName,
-		in2DATName,
-		outDATName
-	});
-
-	WriteColorGroup (in1Col, in1DATName);
-	WriteColorGroup (in2Col, in2DATName);
-	RunExtProgram (Isecalc, cfg::IsecalcPath, argv);
-	InsertOutput (outDATName, false, {});
-}
-
-// =============================================================================
-//
-void MainWindow::actionEdger2()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Edger2))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::Edger2Dialog ui;
-	ui.setupUi (dlg);
-
-	if (not dlg->exec())
-		return;
-
-	QTemporaryFile in, out;
-	QString inName, outName;
-
-	if (not MakeTempFile (in, inName) or not MakeTempFile (out, outName))
-		return;
-
-	int unmatched = ui.unmatched->currentIndex();
-
-	QString argv = Join (
-	{
-		format ("-p %1", ui.precision->value()),
-		format ("-af %1", ui.flatAngle->value()),
-		format ("-ac %1", ui.condAngle->value()),
-		format ("-ae %1", ui.edgeAngle->value()),
-		ui.delLines->isChecked()     ? "-de" : "",
-		ui.delCondLines->isChecked() ? "-dc" : "",
-		ui.colored->isChecked()      ? "-c" : "",
-		ui.bfc->isChecked()          ? "-b" : "",
-		ui.convex->isChecked()       ? "-cx" : "",
-		ui.concave->isChecked()      ? "-cv" : "",
-		unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""),
-		inName,
-		outName,
-	});
-
-	WriteSelection (inName);
-
-	if (not RunExtProgram (Edger2, cfg::Edger2Path, argv))
-		return;
-
-	InsertOutput (outName, true, {});
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/extPrograms.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,721 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu 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 <QFileInfo>
+#include "main.h"
+#include "configuration.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "radioGroup.h"
+#include "editHistory.h"
+#include "ui_ytruder.h"
+#include "ui_intersector.h"
+#include "ui_rectifier.h"
+#include "ui_coverer.h"
+#include "ui_isecalc.h"
+#include "ui_edger2.h"
+#include "dialogs.h"
+
+enum extprog
+{
+	Isecalc,
+	Intersector,
+	Coverer,
+	Ytruder,
+	Rectifier,
+	Edger2,
+};
+
+// =============================================================================
+//
+CFGENTRY (String, IsecalcPath, "")
+CFGENTRY (String, IntersectorPath, "")
+CFGENTRY (String, CovererPath, "")
+CFGENTRY (String, YtruderPath, "")
+CFGENTRY (String, RectifierPath, "")
+CFGENTRY (String, Edger2Path, "")
+
+QString* const g_extProgPaths[] =
+{
+	&cfg::IsecalcPath,
+	&cfg::IntersectorPath,
+	&cfg::CovererPath,
+	&cfg::YtruderPath,
+	&cfg::RectifierPath,
+	&cfg::Edger2Path,
+};
+
+CFGENTRY (Bool, IsecalcUsesWine, false)
+CFGENTRY (Bool, IntersectorUsesWine, false)
+CFGENTRY (Bool, CovererUsesWine, false)
+CFGENTRY (Bool, YtruderUsesWine, false)
+CFGENTRY (Bool, RectifierUsesWine, false)
+CFGENTRY (Bool, Edger2UsesWine, false)
+
+bool* const g_extProgWine[] =
+{
+	&cfg::IsecalcUsesWine,
+	&cfg::IntersectorUsesWine,
+	&cfg::CovererUsesWine,
+	&cfg::YtruderUsesWine,
+	&cfg::RectifierUsesWine,
+	&cfg::Edger2UsesWine,
+};
+
+const char* g_extProgNames[] =
+{
+	"Isecalc",
+	"Intersector",
+	"Coverer",
+	"Ytruder",
+	"Rectifier",
+	"Edger2"
+};
+
+// =============================================================================
+//
+static bool MakeTempFile (QTemporaryFile& tmp, QString& fname)
+{
+	if (not tmp.open())
+		return false;
+
+	fname = tmp.fileName();
+	tmp.close();
+	return true;
+}
+
+// =============================================================================
+//
+static bool CheckExtProgramPath (const extprog prog)
+{
+	QString& path = *g_extProgPaths[prog];
+
+	if (not path.isEmpty())
+		return true;
+
+	ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]);
+
+	if (dlg->exec() and not dlg->getPath().isEmpty())
+	{
+		path = dlg->getPath();
+		return true;
+	}
+
+	return false;
+}
+
+// =============================================================================
+//
+static QString ProcessExtProgError (extprog prog, QProcess& proc)
+{
+	switch (proc.error())
+	{
+		case QProcess::FailedToStart:
+		{
+			QString wineblurb;
+
+#ifndef _WIN32
+			if (*g_extProgWine[prog])
+				wineblurb = "make sure Wine is installed and ";
+#else
+			(void) prog;
+#endif
+
+			return format ("Program failed to start, %1check your permissions", wineblurb);
+		} break;
+
+		case QProcess::Crashed:
+			return "Crashed.";
+
+		case QProcess::WriteError:
+		case QProcess::ReadError:
+			return "I/O error.";
+
+		case QProcess::UnknownError:
+			return "Unknown error";
+
+		case QProcess::Timedout:
+			return format ("Timed out (30 seconds)");
+	}
+
+	return "";
+}
+
+// =============================================================================
+//
+static void WriteObjects (const LDObjectList& objects, QFile& f)
+{
+	for (LDObjectPtr obj : objects)
+	{
+		if (obj->type() == OBJ_Subfile)
+		{
+			LDSubfilePtr ref = obj.staticCast<LDSubfile>();
+			LDObjectList objs = ref->inlineContents (true, false);
+
+			WriteObjects (objs, f);
+
+			for (LDObjectPtr obj : objs)
+				obj->destroy();
+		}
+		else
+			f.write ((obj->asText() + "\r\n").toUtf8());
+	}
+}
+
+// =============================================================================
+//
+static void WriteObjects (const LDObjectList& objects, QString fname)
+{
+	// Write the input file
+	QFile f (fname);
+
+	if (not f.open (QIODevice::WriteOnly | QIODevice::Text))
+	{
+		Critical (format ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString()));
+		return;
+	}
+
+	WriteObjects (objects, f);
+	f.close();
+
+#ifdef DEBUG
+	QFile::copy (fname, "debug_lastInput");
+#endif
+}
+
+// =============================================================================
+//
+void WriteSelection (QString fname)
+{
+	WriteObjects (Selection(), fname);
+}
+
+// =============================================================================
+//
+void WriteColorGroup (LDColor color, QString fname)
+{
+	LDObjectList objects;
+
+	for (LDObjectPtr obj : CurrentDocument()->objects())
+	{
+		if (not obj->isColored() or obj->color() != color)
+			continue;
+
+		objects << obj;
+	}
+
+	WriteObjects (objects, fname);
+}
+
+// =============================================================================
+//
+bool RunExtProgram (extprog prog, QString path, QString argvstr)
+{
+	QTemporaryFile input;
+	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
+
+#ifndef _WIN32
+	if (*g_extProgWine[prog])
+	{
+		argv.insert (0, path);
+		path = "wine";
+	}
+#endif // _WIN32
+
+	print ("Running command: %1 %2\n", path, argv.join (" "));
+
+	if (not input.open())
+		return false;
+
+	QProcess proc;
+
+	// Begin!
+	proc.setStandardInputFile (input.fileName());
+	proc.start (path, argv);
+
+	if (not proc.waitForStarted())
+	{
+		Critical (format ("Couldn't start %1: %2\n", g_extProgNames[prog], ProcessExtProgError (prog, proc)));
+		return false;
+	}
+
+	// Write an enter, the utility tools all expect one
+	input.write ("\n");
+
+	// Wait while it runs
+	proc.waitForFinished();
+
+	QString err = "";
+
+	if (proc.exitStatus() != QProcess::NormalExit)
+		err = ProcessExtProgError (prog, proc);
+
+	// Check the return code
+	if (proc.exitCode() != 0)
+		err = format ("Program exited abnormally (return code %1).",  proc.exitCode());
+
+	if (not err.isEmpty())
+	{
+		Critical (format ("%1 failed: %2\n", g_extProgNames[prog], err));
+		QString filename ("externalProgramOutput.txt");
+		QFile file (filename);
+
+		if (file.open (QIODevice::WriteOnly | QIODevice::Text))
+		{
+			file.write (proc.readAllStandardOutput());
+			file.write (proc.readAllStandardError());
+			print ("Wrote output and error logs to %1", QFileInfo (file).absoluteFilePath());
+		}
+		else
+		{
+			print ("Couldn't open %1 for writing: %2",
+				QFileInfo (filename).absoluteFilePath(), file.errorString());
+		}
+
+		return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+static void InsertOutput (QString fname, bool replace, QList<LDColor> colorsToReplace)
+{
+#ifdef DEBUG
+	QFile::copy (fname, "./debug_lastOutput");
+#endif // RELEASE
+
+	// Read the output file
+	QFile f (fname);
+
+	if (not f.open (QIODevice::ReadOnly))
+	{
+		Critical (format ("Couldn't open temporary file %1 for reading.\n", fname));
+		return;
+	}
+
+	LDObjectList objs = LoadFileContents (&f, null);
+
+	// If we replace the objects, delete the selection now.
+	if (replace)
+		g_win->deleteSelection();
+
+	for (LDColor color : colorsToReplace)
+		g_win->deleteByColor (color);
+
+	// Insert the new objects
+	CurrentDocument()->clearSelection();
+
+	for (LDObjectPtr obj : objs)
+	{
+		if (not obj->isScemantic())
+		{
+			obj->destroy();
+			continue;
+		}
+
+		CurrentDocument()->addObject (obj);
+		obj->select();
+	}
+
+	g_win->doFullRefresh();
+}
+
+// =============================================================================
+// Interface for Ytruder
+// =============================================================================
+void MainWindow::actionYtruder()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Ytruder))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::YtruderUI ui;
+	ui.setupUi (dlg);
+
+	if (not dlg->exec())
+		return;
+
+	// Read the user's choices
+	const enum { Distance, Symmetry, Projection, Radial } mode =
+		ui.mode_distance->isChecked()   ? Distance :
+		ui.mode_symmetry->isChecked()   ? Symmetry :
+		ui.mode_projection->isChecked() ? Projection : Radial;
+
+	const Axis axis =
+		ui.axis_x->isChecked() ? X :
+		ui.axis_y->isChecked() ? Y : Z;
+
+	const double depth = ui.planeDepth->value(),
+				 condAngle = ui.condAngle->value();
+
+	QTemporaryFile indat, outdat;
+	QString inDATName, outDATName;
+
+	// Make temp files for the input and output files
+	if (not MakeTempFile (indat, inDATName) or not MakeTempFile (outdat, outDATName))
+		return;
+
+	// Compose the command-line arguments
+	QString argv = Join (
+	{
+		(axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
+		(mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
+		depth,
+		"-a",
+		condAngle,
+		inDATName,
+		outDATName
+	});
+
+	WriteSelection (inDATName);
+
+	if (not RunExtProgram (Ytruder, cfg::YtruderPath, argv))
+		return;
+
+	InsertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+// Rectifier interface
+// =============================================================================
+void MainWindow::actionRectifier()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Rectifier))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::RectifierUI ui;
+	ui.setupUi (dlg);
+
+	if (not dlg->exec())
+		return;
+
+	QTemporaryFile indat, outdat;
+	QString inDATName, outDATName;
+
+	// Make temp files for the input and output files
+	if (not MakeTempFile (indat, inDATName) or not MakeTempFile (outdat, outDATName))
+		return;
+
+	// Compose arguments
+	QString argv = Join (
+	{
+		(not ui.cb_condense->isChecked()) ? "-q" : "",
+		(not 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 (not RunExtProgram (Rectifier, cfg::RectifierPath, argv))
+		return;
+
+	InsertOutput (outDATName, true, {});
+}
+
+// =============================================================================
+// Intersector interface
+// =============================================================================
+void MainWindow::actionIntersector()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (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.");
+
+	LDColor inCol, cutCol;
+	const bool repeatInverse = ui.cb_repeat->isChecked();
+
+	forever
+	{
+		if (not dlg->exec())
+			return;
+
+		inCol = LDColor::fromIndex (ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt());
+		cutCol = LDColor::fromIndex (ui.cmb_cutcol->itemData (ui.cmb_cutcol->currentIndex()).toInt());
+
+		if (inCol == cutCol)
+		{
+			Critical ("Cannot use the same color group for both input and cutter!");
+			continue;
+		}
+
+		break;
+	}
+
+	// Five temporary files!
+	// indat = input group file
+	// cutdat = cutter group file
+	// outdat = primary output
+	// outdat2 = inverse output
+	// edgesdat = edges output (isecalc)
+	QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat;
+	QString inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
+
+	if (not MakeTempFile (indat, inDATName) or
+		not MakeTempFile (cutdat, cutDATName) or
+		not MakeTempFile (outdat, outDATName) or
+		not MakeTempFile (outdat2, outDAT2Name) or
+		not MakeTempFile (edgesdat, edgesDATName))
+	{
+		return;
+	}
+
+	QString parms = Join (
+	{
+		(ui.cb_colorize->isChecked()) ? "-c" : "",
+		(ui.cb_nocondense->isChecked()) ? "-t" : "",
+		"-s",
+		ui.dsb_prescale->value()
+	});
+
+	QString argv_normal = Join (
+	{
+		parms,
+		inDATName,
+		cutDATName,
+		outDATName
+	});
+
+	QString argv_inverse = Join (
+	{
+		parms,
+		cutDATName,
+		inDATName,
+		outDAT2Name
+	});
+
+	WriteColorGroup (inCol, inDATName);
+	WriteColorGroup (cutCol, cutDATName);
+
+	if (not RunExtProgram (Intersector, cfg::IntersectorPath, argv_normal))
+		return;
+
+	InsertOutput (outDATName, false, {inCol});
+
+	if (repeatInverse and RunExtProgram (Intersector, cfg::IntersectorPath, argv_inverse))
+		InsertOutput (outDAT2Name, false, {cutCol});
+
+	if (ui.cb_edges->isChecked() and CheckExtProgramPath (Isecalc) and
+		RunExtProgram (Isecalc, cfg::IsecalcPath, Join ({inDATName, cutDATName, edgesDATName})))
+	{
+		InsertOutput (edgesDATName, false, {});
+	}
+}
+
+// =============================================================================
+//
+void MainWindow::actionCoverer()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Coverer))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::CovererUI ui;
+	ui.setupUi (dlg);
+	MakeColorComboBox (ui.cmb_col1);
+	MakeColorComboBox (ui.cmb_col2);
+
+	LDColor in1Col, in2Col;
+
+	forever
+	{
+		if (not dlg->exec())
+			return;
+
+		in1Col = LDColor::fromIndex (ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt());
+		in2Col = LDColor::fromIndex (ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt());
+
+		if (in1Col == in2Col)
+		{
+			Critical ("Cannot use the same color group for both inputs!");
+			continue;
+		}
+
+		break;
+	}
+
+	QTemporaryFile in1dat, in2dat, outdat;
+	QString in1DATName, in2DATName, outDATName;
+
+	if (not MakeTempFile (in1dat, in1DATName) or
+		not MakeTempFile (in2dat, in2DATName) or
+		not MakeTempFile (outdat, outDATName))
+	{
+		return;
+	}
+
+	QString argv = Join (
+	{
+		(ui.cb_oldsweep->isChecked() ? "-s" : ""),
+		(ui.cb_reverse->isChecked() ? "-r" : ""),
+		(ui.dsb_segsplit->value() != 0 ? format ("-l %1", ui.dsb_segsplit->value()) : ""),
+		(ui.sb_bias->value() != 0 ? format ("-s %1", ui.sb_bias->value()) : ""),
+		in1DATName,
+		in2DATName,
+		outDATName
+	});
+
+	WriteColorGroup (in1Col, in1DATName);
+	WriteColorGroup (in2Col, in2DATName);
+
+	if (not RunExtProgram (Coverer, cfg::CovererPath, argv))
+		return;
+
+	InsertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+//
+void MainWindow::actionIsecalc()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Isecalc))
+		return;
+
+	Ui::IsecalcUI ui;
+	QDialog* dlg = new QDialog;
+	ui.setupUi (dlg);
+
+	MakeColorComboBox (ui.cmb_col1);
+	MakeColorComboBox (ui.cmb_col2);
+
+	LDColor in1Col, in2Col;
+
+	// Run the dialog and validate input
+	forever
+	{
+		if (not dlg->exec())
+			return;
+
+		in1Col = LDColor::fromIndex (ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt());
+		in2Col = LDColor::fromIndex (ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt());
+
+		if (in1Col == in2Col)
+		{
+			Critical ("Cannot use the same color group for both input and cutter!");
+			continue;
+		}
+
+		break;
+	}
+
+	QTemporaryFile in1dat, in2dat, outdat;
+	QString in1DATName, in2DATName, outDATName;
+
+	if (not MakeTempFile (in1dat, in1DATName) or
+		not MakeTempFile (in2dat, in2DATName) or
+		not MakeTempFile (outdat, outDATName))
+	{
+		return;
+	}
+
+	QString argv = Join (
+	{
+		in1DATName,
+		in2DATName,
+		outDATName
+	});
+
+	WriteColorGroup (in1Col, in1DATName);
+	WriteColorGroup (in2Col, in2DATName);
+	RunExtProgram (Isecalc, cfg::IsecalcPath, argv);
+	InsertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+//
+void MainWindow::actionEdger2()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Edger2))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::Edger2Dialog ui;
+	ui.setupUi (dlg);
+
+	if (not dlg->exec())
+		return;
+
+	QTemporaryFile in, out;
+	QString inName, outName;
+
+	if (not MakeTempFile (in, inName) or not MakeTempFile (out, outName))
+		return;
+
+	int unmatched = ui.unmatched->currentIndex();
+
+	QString argv = Join (
+	{
+		format ("-p %1", ui.precision->value()),
+		format ("-af %1", ui.flatAngle->value()),
+		format ("-ac %1", ui.condAngle->value()),
+		format ("-ae %1", ui.edgeAngle->value()),
+		ui.delLines->isChecked()     ? "-de" : "",
+		ui.delCondLines->isChecked() ? "-dc" : "",
+		ui.colored->isChecked()      ? "-c" : "",
+		ui.bfc->isChecked()          ? "-b" : "",
+		ui.convex->isChecked()       ? "-cx" : "",
+		ui.concave->isChecked()      ? "-cv" : "",
+		unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""),
+		inName,
+		outName,
+	});
+
+	WriteSelection (inName);
+
+	if (not RunExtProgram (Edger2, cfg::Edger2Path, argv))
+		return;
+
+	InsertOutput (outName, true, {});
+}
--- a/src/glCompiler.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,423 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu 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/>.
- */
-
-#define GL_GLEXT_PROTOTYPES
-#include <GL/glu.h>
-#include <GL/glext.h>
-#include "glCompiler.h"
-#include "ldObject.h"
-#include "colors.h"
-#include "ldDocument.h"
-#include "miscallenous.h"
-#include "glRenderer.h"
-#include "dialogs.h"
-
-struct GLErrorInfo
-{
-	GLenum	value;
-	QString	text;
-};
-
-static const GLErrorInfo g_GLErrors[] =
-{
-	{ GL_NO_ERROR,						"No error" },
-	{ GL_INVALID_ENUM,					"Unacceptable enumerator passed" },
-	{ GL_INVALID_VALUE,					"Numeric argument out of range" },
-	{ GL_INVALID_OPERATION,				"The operation is not allowed to be done in this state" },
-	{ GL_INVALID_FRAMEBUFFER_OPERATION,	"Framebuffer object is not complete"},
-	{ GL_OUT_OF_MEMORY,					"Out of memory" },
-	{ GL_STACK_UNDERFLOW,				"The operation would have caused an underflow" },
-	{ GL_STACK_OVERFLOW,				"The operation would have caused an overflow" },
-};
-
-CFGENTRY (String, SelectColorBlend, "#0080FF")
-EXTERN_CFGENTRY (Bool, BlackEdges)
-EXTERN_CFGENTRY (String, BackgroundColor)
-
-static QList<int>		g_warnedColors;
-static const QColor		g_BFCFrontColor (64, 192, 80);
-static const QColor		g_BFCBackColor (208, 64, 64);
-
-// static QMap<LDObjectPtr, String> g_objectOrigins;
-
-// =============================================================================
-//
-void CheckGLErrorImpl (const char* file, int line)
-{
-	QString errmsg;
-	GLenum errnum = glGetError();
-
-	if (errnum == GL_NO_ERROR)
-		return;
-
-	for (const GLErrorInfo& err : g_GLErrors)
-	{
-		if (err.value == errnum)
-		{
-			errmsg = err.text;
-			break;
-		}
-	}
-
-	print ("OpenGL ERROR: at %1:%2: %3", Basename (QString (file)), line, errmsg);
-}
-
-// =============================================================================
-//
-GLCompiler::GLCompiler (GLRenderer* renderer) :
-	m_renderer (renderer)
-{
-	needMerge();
-	memset (m_vboSizes, 0, sizeof m_vboSizes);
-}
-
-// =============================================================================
-//
-void GLCompiler::initialize()
-{
-	initializeOpenGLFunctions();
-	glGenBuffers (g_numVBOs, &m_vbo[0]);
-	CHECK_GL_ERROR();
-}
-
-// =============================================================================
-//
-GLCompiler::~GLCompiler()
-{
-	glDeleteBuffers (g_numVBOs, &m_vbo[0]);
-	CHECK_GL_ERROR();
-
-	if (m_renderer != null)
-		m_renderer->setCompiler (null);
-}
-
-// =============================================================================
-//
-uint32 GLCompiler::colorToRGB (const QColor& color)
-{
-	return
-		(color.red()   & 0xFF) << 0x00 |
-		(color.green() & 0xFF) << 0x08 |
-		(color.blue()  & 0xFF) << 0x10 |
-		(color.alpha() & 0xFF) << 0x18;
-}
-
-// =============================================================================
-//
-QColor GLCompiler::indexColorForID (int id) const
-{
-	// Calculate a color based from this index. This method caters for
-	// 16777216 objects. I don't think that will be exceeded anytime soon. :)
-	int r = (id / 0x10000) % 0x100,
-		g = (id / 0x100) % 0x100,
-		b = id % 0x100;
-
-	return QColor (r, g, b);
-}
-
-// =============================================================================
-//
-QColor GLCompiler::getColorForPolygon (LDPolygon& poly, LDObjectPtr topobj,
-									   EVBOComplement complement) const
-{
-	QColor qcol;
-
-	switch (complement)
-	{
-		case VBOCM_Surfaces:
-		case VBOCM_NumComplements:
-			return QColor();
-
-		case VBOCM_BFCFrontColors:
-			qcol = g_BFCFrontColor;
-			break;
-
-		case VBOCM_BFCBackColors:
-			qcol = g_BFCBackColor;
-			break;
-
-		case VBOCM_PickColors:
-			return indexColorForID (topobj->id());
-
-		case VBOCM_RandomColors:
-			qcol = topobj->randomColor();
-			break;
-
-		case VBOCM_NormalColors:
-			if (poly.color == MainColorIndex)
-			{
-				if (topobj->color() == MainColor())
-					qcol = GLRenderer::getMainColor();
-				else
-					qcol = topobj->color().faceColor();
-			}
-			elif (poly.color == EdgeColorIndex)
-			{
-				qcol = Luma (QColor (cfg::BackgroundColor)) > 40 ? Qt::black : Qt::white;
-			}
-			else
-			{
-				LDColor col = LDColor::fromIndex (poly.color);
-
-				if (col)
-					qcol = col.faceColor();
-			}
-			break;
-	}
-
-	if (not qcol.isValid())
-	{
-		// The color was unknown. Use main color to make the polygon at least
-		// not appear pitch-black.
-		if (poly.num != 2 and poly.num != 5)
-			qcol = GLRenderer::getMainColor();
-		else
-			qcol = Qt::black;
-
-		// Warn about the unknown color, but only once.
-		if (not g_warnedColors.contains (poly.color))
-		{
-			print ("Unknown color %1!\n", poly.color);
-			g_warnedColors << poly.color;
-		}
-
-		return qcol;
-	}
-
-	double blendAlpha = 0.0;
-
-	if (topobj->isSelected())
-		blendAlpha = 1.0;
-	elif (topobj == m_renderer->objectAtCursor())
-		blendAlpha = 0.5;
-
-	if (blendAlpha != 0.0)
-	{
-		QColor selcolor (cfg::SelectColorBlend);
-		double denom = blendAlpha + 1.0;
-		qcol.setRed ((qcol.red() + (selcolor.red() * blendAlpha)) / denom);
-		qcol.setGreen ((qcol.green() + (selcolor.green() * blendAlpha)) / denom);
-		qcol.setBlue ((qcol.blue() + (selcolor.blue() * blendAlpha)) / denom);
-	}
-
-	return qcol;
-}
-
-// =============================================================================
-//
-void GLCompiler::needMerge()
-{
-	for (int i = 0; i < countof (m_vboChanged); ++i)
-		m_vboChanged[i] = true;
-}
-
-// =============================================================================
-//
-void GLCompiler::stageForCompilation (LDObjectPtr obj)
-{
-	/*
-	g_objectOrigins[obj] = format ("%1:%2 (%3)",
-		obj->document()->getDisplayName(), obj->lineNumber(), obj->typeName());
-	*/
-
-	m_staged << LDObjectWeakPtr (obj);
-}
-
-// =============================================================================
-//
-void GLCompiler::unstage (LDObjectPtr obj)
-{
-	m_staged.removeOne (LDObjectWeakPtr (obj));
-}
-
-// =============================================================================
-//
-void GLCompiler::compileDocument (LDDocumentPtr doc)
-{
-	if (doc == null)
-		return;
-
-	for (LDObjectPtr obj : doc->objects())
-		compileObject (obj);
-}
-
-// =============================================================================
-//
-void GLCompiler::compileStaged()
-{
-	RemoveDuplicates (m_staged);
-
-	for (auto it = m_staged.begin(); it != m_staged.end(); ++it)
-	{
-		if (*it == null)
-			continue;
-
-		compileObject (*it);
-	}
-
-	m_staged.clear();
-}
-
-// =============================================================================
-//
-void GLCompiler::prepareVBO (int vbonum)
-{
-	// Compile anything that still awaits it
-	compileStaged();
-
-	if (not m_vboChanged[vbonum])
-		return;
-
-	QVector<GLfloat> vbodata;
-
-	for (auto it = m_objectInfo.begin(); it != m_objectInfo.end();)
-	{
-		if (it.key() == null)
-		{
-			it = m_objectInfo.erase (it);
-			continue;
-		}
-
-		if (it.key().toStrongRef()->document() == CurrentDocument()
-			and not it.key().toStrongRef()->isHidden())
-		{
-			vbodata += it->data[vbonum];
-		}
-
-		++it;
-	}
-
-	glBindBuffer (GL_ARRAY_BUFFER, m_vbo[vbonum]);
-	glBufferData (GL_ARRAY_BUFFER, vbodata.size() * sizeof(GLfloat), vbodata.constData(), GL_STATIC_DRAW);
-	glBindBuffer (GL_ARRAY_BUFFER, 0);
-	CHECK_GL_ERROR();
-	m_vboChanged[vbonum] = false;
-	m_vboSizes[vbonum] = vbodata.size();
-}
-
-// =============================================================================
-//
-void GLCompiler::dropObject (LDObjectPtr obj)
-{
-	auto it = m_objectInfo.find (obj);
-
-	if (it != m_objectInfo.end())
-	{
-		m_objectInfo.erase (it);
-		needMerge();
-	}
-
-	unstage (obj);
-}
-
-// =============================================================================
-//
-void GLCompiler::compileObject (LDObjectPtr obj)
-{
-//	print ("Compile %1\n", g_objectOrigins[obj]);
-
-	if (obj == null or obj->document() == null or obj->document().toStrongRef()->isImplicit())
-		return;
-
-	ObjectVBOInfo info;
-	info.isChanged = true;
-	dropObject (obj);
-
-	switch (obj->type())
-	{
-		// Note: We cannot split quads into triangles here, it would mess up the
-		// wireframe view. Quads must go into separate vbos.
-		case OBJ_Triangle:
-		case OBJ_Quad:
-		case OBJ_Line:
-		case OBJ_CondLine:
-		{
-			LDPolygon* poly = obj->getPolygon();
-			poly->id = obj->id();
-			compilePolygon (*poly, obj, &info);
-			delete poly;
-			break;
-		}
-
-		case OBJ_Subfile:
-		{
-			LDSubfilePtr ref = obj.staticCast<LDSubfile>();
-			auto data = ref->inlinePolygons();
-
-			for (LDPolygon& poly : data)
-			{
-				poly.id = obj->id();
-				compilePolygon (poly, obj, &info);
-			}
-			break;
-		}
-
-		default:
-			break;
-	}
-
-	m_objectInfo[obj] = info;
-	needMerge();
-}
-
-// =============================================================================
-//
-void GLCompiler::compilePolygon (LDPolygon& poly, LDObjectPtr topobj, ObjectVBOInfo* objinfo)
-{
-	EVBOSurface surface;
-	int numverts;
-
-	switch (poly.num)
-	{
-		case 2:	surface = VBOSF_Lines;		numverts = 2; break;
-		case 3:	surface = VBOSF_Triangles;	numverts = 3; break;
-		case 4:	surface = VBOSF_Quads;		numverts = 4; break;
-		case 5:	surface = VBOSF_CondLines;	numverts = 2; break;
-		default: return;
-	}
-
-	for (EVBOComplement complement = VBOCM_First; complement < VBOCM_NumComplements; ++complement)
-	{
-		const int vbonum			= vboNumber (surface, complement);
-		QVector<GLfloat>& vbodata	= objinfo->data[vbonum];
-		const QColor color			= getColorForPolygon (poly, topobj, complement);
-
-		for (int vert = 0; vert < numverts; ++vert)
-		{
-			if (complement == VBOCM_Surfaces)
-			{
-				// Write coordinates. Apparently Z must be flipped too?
-				vbodata	<< poly.vertices[vert].x()
-						<< -poly.vertices[vert].y()
-						<< -poly.vertices[vert].z();
-			}
-			else
-			{
-				vbodata	<< ((GLfloat) color.red()) / 255.0f
-						<< ((GLfloat) color.green()) / 255.0f
-						<< ((GLfloat) color.blue()) / 255.0f
-						<< ((GLfloat) color.alpha()) / 255.0f;
-			}
-		}
-	}
-}
-
-void GLCompiler::setRenderer (GLRenderer* renderer)
-{
-	m_renderer = renderer;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glCompiler.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,423 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu 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/>.
+ */
+
+#define GL_GLEXT_PROTOTYPES
+#include <GL/glu.h>
+#include <GL/glext.h>
+#include "glCompiler.h"
+#include "ldObject.h"
+#include "colors.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "glRenderer.h"
+#include "dialogs.h"
+
+struct GLErrorInfo
+{
+	GLenum	value;
+	QString	text;
+};
+
+static const GLErrorInfo g_GLErrors[] =
+{
+	{ GL_NO_ERROR,						"No error" },
+	{ GL_INVALID_ENUM,					"Unacceptable enumerator passed" },
+	{ GL_INVALID_VALUE,					"Numeric argument out of range" },
+	{ GL_INVALID_OPERATION,				"The operation is not allowed to be done in this state" },
+	{ GL_INVALID_FRAMEBUFFER_OPERATION,	"Framebuffer object is not complete"},
+	{ GL_OUT_OF_MEMORY,					"Out of memory" },
+	{ GL_STACK_UNDERFLOW,				"The operation would have caused an underflow" },
+	{ GL_STACK_OVERFLOW,				"The operation would have caused an overflow" },
+};
+
+CFGENTRY (String, SelectColorBlend, "#0080FF")
+EXTERN_CFGENTRY (Bool, BlackEdges)
+EXTERN_CFGENTRY (String, BackgroundColor)
+
+static QList<int>		g_warnedColors;
+static const QColor		g_BFCFrontColor (64, 192, 80);
+static const QColor		g_BFCBackColor (208, 64, 64);
+
+// static QMap<LDObjectPtr, String> g_objectOrigins;
+
+// =============================================================================
+//
+void CheckGLErrorImpl (const char* file, int line)
+{
+	QString errmsg;
+	GLenum errnum = glGetError();
+
+	if (errnum == GL_NO_ERROR)
+		return;
+
+	for (const GLErrorInfo& err : g_GLErrors)
+	{
+		if (err.value == errnum)
+		{
+			errmsg = err.text;
+			break;
+		}
+	}
+
+	print ("OpenGL ERROR: at %1:%2: %3", Basename (QString (file)), line, errmsg);
+}
+
+// =============================================================================
+//
+GLCompiler::GLCompiler (GLRenderer* renderer) :
+	m_renderer (renderer)
+{
+	needMerge();
+	memset (m_vboSizes, 0, sizeof m_vboSizes);
+}
+
+// =============================================================================
+//
+void GLCompiler::initialize()
+{
+	initializeOpenGLFunctions();
+	glGenBuffers (g_numVBOs, &m_vbo[0]);
+	CHECK_GL_ERROR();
+}
+
+// =============================================================================
+//
+GLCompiler::~GLCompiler()
+{
+	glDeleteBuffers (g_numVBOs, &m_vbo[0]);
+	CHECK_GL_ERROR();
+
+	if (m_renderer != null)
+		m_renderer->setCompiler (null);
+}
+
+// =============================================================================
+//
+uint32 GLCompiler::colorToRGB (const QColor& color)
+{
+	return
+		(color.red()   & 0xFF) << 0x00 |
+		(color.green() & 0xFF) << 0x08 |
+		(color.blue()  & 0xFF) << 0x10 |
+		(color.alpha() & 0xFF) << 0x18;
+}
+
+// =============================================================================
+//
+QColor GLCompiler::indexColorForID (int id) const
+{
+	// Calculate a color based from this index. This method caters for
+	// 16777216 objects. I don't think that will be exceeded anytime soon. :)
+	int r = (id / 0x10000) % 0x100,
+		g = (id / 0x100) % 0x100,
+		b = id % 0x100;
+
+	return QColor (r, g, b);
+}
+
+// =============================================================================
+//
+QColor GLCompiler::getColorForPolygon (LDPolygon& poly, LDObjectPtr topobj,
+									   EVBOComplement complement) const
+{
+	QColor qcol;
+
+	switch (complement)
+	{
+		case VBOCM_Surfaces:
+		case VBOCM_NumComplements:
+			return QColor();
+
+		case VBOCM_BFCFrontColors:
+			qcol = g_BFCFrontColor;
+			break;
+
+		case VBOCM_BFCBackColors:
+			qcol = g_BFCBackColor;
+			break;
+
+		case VBOCM_PickColors:
+			return indexColorForID (topobj->id());
+
+		case VBOCM_RandomColors:
+			qcol = topobj->randomColor();
+			break;
+
+		case VBOCM_NormalColors:
+			if (poly.color == MainColorIndex)
+			{
+				if (topobj->color() == MainColor())
+					qcol = GLRenderer::getMainColor();
+				else
+					qcol = topobj->color().faceColor();
+			}
+			elif (poly.color == EdgeColorIndex)
+			{
+				qcol = Luma (QColor (cfg::BackgroundColor)) > 40 ? Qt::black : Qt::white;
+			}
+			else
+			{
+				LDColor col = LDColor::fromIndex (poly.color);
+
+				if (col)
+					qcol = col.faceColor();
+			}
+			break;
+	}
+
+	if (not qcol.isValid())
+	{
+		// The color was unknown. Use main color to make the polygon at least
+		// not appear pitch-black.
+		if (poly.num != 2 and poly.num != 5)
+			qcol = GLRenderer::getMainColor();
+		else
+			qcol = Qt::black;
+
+		// Warn about the unknown color, but only once.
+		if (not g_warnedColors.contains (poly.color))
+		{
+			print ("Unknown color %1!\n", poly.color);
+			g_warnedColors << poly.color;
+		}
+
+		return qcol;
+	}
+
+	double blendAlpha = 0.0;
+
+	if (topobj->isSelected())
+		blendAlpha = 1.0;
+	elif (topobj == m_renderer->objectAtCursor())
+		blendAlpha = 0.5;
+
+	if (blendAlpha != 0.0)
+	{
+		QColor selcolor (cfg::SelectColorBlend);
+		double denom = blendAlpha + 1.0;
+		qcol.setRed ((qcol.red() + (selcolor.red() * blendAlpha)) / denom);
+		qcol.setGreen ((qcol.green() + (selcolor.green() * blendAlpha)) / denom);
+		qcol.setBlue ((qcol.blue() + (selcolor.blue() * blendAlpha)) / denom);
+	}
+
+	return qcol;
+}
+
+// =============================================================================
+//
+void GLCompiler::needMerge()
+{
+	for (int i = 0; i < countof (m_vboChanged); ++i)
+		m_vboChanged[i] = true;
+}
+
+// =============================================================================
+//
+void GLCompiler::stageForCompilation (LDObjectPtr obj)
+{
+	/*
+	g_objectOrigins[obj] = format ("%1:%2 (%3)",
+		obj->document()->getDisplayName(), obj->lineNumber(), obj->typeName());
+	*/
+
+	m_staged << LDObjectWeakPtr (obj);
+}
+
+// =============================================================================
+//
+void GLCompiler::unstage (LDObjectPtr obj)
+{
+	m_staged.removeOne (LDObjectWeakPtr (obj));
+}
+
+// =============================================================================
+//
+void GLCompiler::compileDocument (LDDocumentPtr doc)
+{
+	if (doc == null)
+		return;
+
+	for (LDObjectPtr obj : doc->objects())
+		compileObject (obj);
+}
+
+// =============================================================================
+//
+void GLCompiler::compileStaged()
+{
+	RemoveDuplicates (m_staged);
+
+	for (auto it = m_staged.begin(); it != m_staged.end(); ++it)
+	{
+		if (*it == null)
+			continue;
+
+		compileObject (*it);
+	}
+
+	m_staged.clear();
+}
+
+// =============================================================================
+//
+void GLCompiler::prepareVBO (int vbonum)
+{
+	// Compile anything that still awaits it
+	compileStaged();
+
+	if (not m_vboChanged[vbonum])
+		return;
+
+	QVector<GLfloat> vbodata;
+
+	for (auto it = m_objectInfo.begin(); it != m_objectInfo.end();)
+	{
+		if (it.key() == null)
+		{
+			it = m_objectInfo.erase (it);
+			continue;
+		}
+
+		if (it.key().toStrongRef()->document() == CurrentDocument()
+			and not it.key().toStrongRef()->isHidden())
+		{
+			vbodata += it->data[vbonum];
+		}
+
+		++it;
+	}
+
+	glBindBuffer (GL_ARRAY_BUFFER, m_vbo[vbonum]);
+	glBufferData (GL_ARRAY_BUFFER, vbodata.size() * sizeof(GLfloat), vbodata.constData(), GL_STATIC_DRAW);
+	glBindBuffer (GL_ARRAY_BUFFER, 0);
+	CHECK_GL_ERROR();
+	m_vboChanged[vbonum] = false;
+	m_vboSizes[vbonum] = vbodata.size();
+}
+
+// =============================================================================
+//
+void GLCompiler::dropObject (LDObjectPtr obj)
+{
+	auto it = m_objectInfo.find (obj);
+
+	if (it != m_objectInfo.end())
+	{
+		m_objectInfo.erase (it);
+		needMerge();
+	}
+
+	unstage (obj);
+}
+
+// =============================================================================
+//
+void GLCompiler::compileObject (LDObjectPtr obj)
+{
+//	print ("Compile %1\n", g_objectOrigins[obj]);
+
+	if (obj == null or obj->document() == null or obj->document().toStrongRef()->isImplicit())
+		return;
+
+	ObjectVBOInfo info;
+	info.isChanged = true;
+	dropObject (obj);
+
+	switch (obj->type())
+	{
+		// Note: We cannot split quads into triangles here, it would mess up the
+		// wireframe view. Quads must go into separate vbos.
+		case OBJ_Triangle:
+		case OBJ_Quad:
+		case OBJ_Line:
+		case OBJ_CondLine:
+		{
+			LDPolygon* poly = obj->getPolygon();
+			poly->id = obj->id();
+			compilePolygon (*poly, obj, &info);
+			delete poly;
+			break;
+		}
+
+		case OBJ_Subfile:
+		{
+			LDSubfilePtr ref = obj.staticCast<LDSubfile>();
+			auto data = ref->inlinePolygons();
+
+			for (LDPolygon& poly : data)
+			{
+				poly.id = obj->id();
+				compilePolygon (poly, obj, &info);
+			}
+			break;
+		}
+
+		default:
+			break;
+	}
+
+	m_objectInfo[obj] = info;
+	needMerge();
+}
+
+// =============================================================================
+//
+void GLCompiler::compilePolygon (LDPolygon& poly, LDObjectPtr topobj, ObjectVBOInfo* objinfo)
+{
+	EVBOSurface surface;
+	int numverts;
+
+	switch (poly.num)
+	{
+		case 2:	surface = VBOSF_Lines;		numverts = 2; break;
+		case 3:	surface = VBOSF_Triangles;	numverts = 3; break;
+		case 4:	surface = VBOSF_Quads;		numverts = 4; break;
+		case 5:	surface = VBOSF_CondLines;	numverts = 2; break;
+		default: return;
+	}
+
+	for (EVBOComplement complement = VBOCM_First; complement < VBOCM_NumComplements; ++complement)
+	{
+		const int vbonum			= vboNumber (surface, complement);
+		QVector<GLfloat>& vbodata	= objinfo->data[vbonum];
+		const QColor color			= getColorForPolygon (poly, topobj, complement);
+
+		for (int vert = 0; vert < numverts; ++vert)
+		{
+			if (complement == VBOCM_Surfaces)
+			{
+				// Write coordinates. Apparently Z must be flipped too?
+				vbodata	<< poly.vertices[vert].x()
+						<< -poly.vertices[vert].y()
+						<< -poly.vertices[vert].z();
+			}
+			else
+			{
+				vbodata	<< ((GLfloat) color.red()) / 255.0f
+						<< ((GLfloat) color.green()) / 255.0f
+						<< ((GLfloat) color.blue()) / 255.0f
+						<< ((GLfloat) color.alpha()) / 255.0f;
+			}
+		}
+	}
+}
+
+void GLCompiler::setRenderer (GLRenderer* renderer)
+{
+	m_renderer = renderer;
+}
--- a/src/glRenderer.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1642 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu 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/>.
- */
-
-#define GL_GLEXT_PROTOTYPES
-#include <GL/glu.h>
-#include <GL/glext.h>
-#include <QGLWidget>
-#include <QWheelEvent>
-#include <QMouseEvent>
-#include <QContextMenuEvent>
-#include <QInputDialog>
-#include <QToolTip>
-#include <QTextDocument>
-#include <QTimer>
-#include <GL/glu.h>
-#include "main.h"
-#include "configuration.h"
-#include "ldDocument.h"
-#include "glRenderer.h"
-#include "colors.h"
-#include "mainWindow.h"
-#include "miscallenous.h"
-#include "editHistory.h"
-#include "dialogs.h"
-#include "addObjectDialog.h"
-#include "messageLog.h"
-#include "glCompiler.h"
-#include "primitives.h"
-
-const LDFixedCamera g_FixedCameras[6] =
-{
-	{{  1,  0, 0 }, X, Z, false, false, false }, // top
-	{{  0,  0, 0 }, X, Y, false,  true, false }, // front
-	{{  0,  1, 0 }, Z, Y,  true,  true, false }, // left
-	{{ -1,  0, 0 }, X, Z, false,  true, true }, // bottom
-	{{  0,  0, 0 }, X, Y,  true,  true, true }, // back
-	{{  0, -1, 0 }, Z, Y, false,  true, true }, // right
-};
-
-CFGENTRY (String, BackgroundColor,			"#FFFFFF")
-CFGENTRY (String, MainColor,					"#A0A0A0")
-CFGENTRY (Float, MainColorAlpha,				1.0)
-CFGENTRY (Int, LineThickness,				2)
-CFGENTRY (Bool, BFCRedGreenView,			false)
-CFGENTRY (Int, Camera,						EFreeCamera)
-CFGENTRY (Bool, BlackEdges,					false)
-CFGENTRY (Bool, DrawAxes,					false)
-CFGENTRY (Bool, DrawWireframe,				false)
-CFGENTRY (Bool, UseLogoStuds,				false)
-CFGENTRY (Bool, AntiAliasedLines,			true)
-CFGENTRY (Bool, RandomColors,				false)
-CFGENTRY (Bool, HighlightObjectBelowCursor,	true)
-CFGENTRY (Bool, DrawSurfaces,				true)
-CFGENTRY (Bool, DrawEdgeLines,				true)
-CFGENTRY (Bool, DrawConditionalLines,		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")
-};
-
-struct LDGLAxis
-{
-	const QColor col;
-	const Vertex vert;
-};
-
-// Definitions for visual axes, drawn on the screen
-static const LDGLAxis g_GLAxes[3] =
-{
-	{ QColor (192,  96,  96), Vertex (10000, 0, 0) }, // X
-	{ QColor (48,  192,  48), Vertex (0, 10000, 0) }, // Y
-	{ QColor (48,  112, 192), Vertex (0, 0, 10000) }, // Z
-};
-
-static bool RendererInitialized (false);
-
-// =============================================================================
-//
-GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent)
-{
-	m_isPicking = false;
-	m_camera = (ECamera) cfg::Camera;
-	m_drawToolTip = false;
-	m_editmode = AbstractEditMode::createByType (this, EditModeType::Select);
-	m_panning = false;
-	m_compiler = new GLCompiler (this);
-	setDrawOnly (false);
-	setMessageLog (null);
-	m_width = m_height = -1;
-	m_position3D = Origin;
-	m_toolTipTimer = new QTimer (this);
-	m_toolTipTimer->setSingleShot (true);
-	m_isCameraMoving = false;
-	m_thinBorderPen = QPen (QColor (0, 0, 0, 208), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
-	m_thinBorderPen.setWidth (1);
-	setAcceptDrops (true);
-	connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer()));
-
-	// Init camera icons
-	for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam)
-	{
-		QString iconname = format ("camera-%1", tr (g_CameraNames[cam]).toLower());
-		CameraIcon* info = &m_cameraIcons[cam];
-		info->img = new QPixmap (GetIcon (iconname));
-		info->cam = cam;
-	}
-
-	calcCameraIcons();
-}
-
-// =============================================================================
-//
-GLRenderer::~GLRenderer()
-{
-	for (int i = 0; i < 6; ++i)
-		delete currentDocumentData().overlays[i].img;
-
-	for (CameraIcon& info : m_cameraIcons)
-		delete info.img;
-
-	if (messageLog())
-		messageLog()->setRenderer (null);
-
-	m_compiler->setRenderer (null);
-	delete m_compiler;
-	delete m_editmode;
-
-	glDeleteBuffers (1, &m_axesVBO);
-	glDeleteBuffers (1, &m_axesColorVBO);
-}
-
-// =============================================================================
-// 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 (cfg::AntiAliasedLines)
-	{
-		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::needZoomToFit()
-{
-	if (document() != null)
-		currentDocumentData().needZoomToFit = true;
-}
-
-// =============================================================================
-//
-void GLRenderer::resetAngles()
-{
-	rot (X) = 30.0f;
-	rot (Y) = 325.f;
-	pan (X) = pan (Y) = rot (Z) = 0.0f;
-	needZoomToFit();
-}
-
-// =============================================================================
-//
-void GLRenderer::resetAllAngles()
-{
-	ECamera oldcam = camera();
-
-	for (int i = 0; i < 7; ++i)
-	{
-		setCamera ((ECamera) i);
-		resetAngles();
-	}
-
-	setCamera (oldcam);
-}
-
-// =============================================================================
-//
-void GLRenderer::initializeGL()
-{
-#ifdef USE_QT5
-	initializeOpenGLFunctions();
-#endif
-	setBackground();
-	glLineWidth (cfg::LineThickness);
-	glLineStipple (1, 0x6666);
-	setAutoFillBackground (false);
-	setMouseTracking (true);
-	setFocusPolicy (Qt::WheelFocus);
-	compiler()->initialize();
-	initializeAxes();
-	RendererInitialized = true;
-}
-
-// =============================================================================
-//
-void GLRenderer::initializeAxes()
-{
-	float axesdata[18];
-	float colordata[18];
-	memset (axesdata, 0, sizeof axesdata);
-
-	for (int i = 0; i < 3; ++i)
-	{
-		for_axes (ax)
-		{
-			axesdata[(i * 6) + ax] = g_GLAxes[i].vert[ax];
-			axesdata[(i * 6) + 3 + ax] = -g_GLAxes[i].vert[ax];
-		}
-
-		for (int j = 0; j < 2; ++j)
-		{
-			colordata[(i * 6) + (j * 3) + 0] = g_GLAxes[i].col.red();
-			colordata[(i * 6) + (j * 3) + 1] = g_GLAxes[i].col.green();
-			colordata[(i * 6) + (j * 3) + 2] = g_GLAxes[i].col.blue();
-		}
-	}
-
-	glGenBuffers (1, &m_axesVBO);
-	glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO);
-	glBufferData (GL_ARRAY_BUFFER, sizeof axesdata, axesdata, GL_STATIC_DRAW);
-	glGenBuffers (1, &m_axesColorVBO);
-	glBindBuffer (GL_ARRAY_BUFFER, m_axesColorVBO);
-	glBufferData (GL_ARRAY_BUFFER, sizeof colordata, colordata, GL_STATIC_DRAW);
-	glBindBuffer (GL_ARRAY_BUFFER, 0);
-}
-
-// =============================================================================
-//
-QColor GLRenderer::getMainColor()
-{
-	QColor col (cfg::MainColor);
-
-	if (not col.isValid())
-		return QColor (0, 0, 0);
-
-	col.setAlpha (cfg::MainColorAlpha * 255.f);
-	return col;
-}
-
-// =============================================================================
-//
-void GLRenderer::setBackground()
-{
-	if (isPicking())
-	{
-		glClearColor (0.0f, 0.0f, 0.0f, 1.0f);
-		return;
-	}
-
-	QColor col (cfg::BackgroundColor);
-
-	if (not col.isValid())
-		return;
-
-	col.setAlpha (255);
-
-	m_darkbg = Luma (col) < 80;
-	m_bgcolor = col;
-	qglClearColor (col);
-}
-
-// =============================================================================
-//
-void GLRenderer::refresh()
-{
-	update();
-
-	if (isVisible())
-		swapBuffers();
-}
-
-// =============================================================================
-//
-void GLRenderer::hardRefresh()
-{
-	if (not RendererInitialized)
-		return;
-
-	compiler()->compileDocument (CurrentDocument());
-	refresh();
-}
-
-// =============================================================================
-//
-void GLRenderer::resizeGL (int w, int h)
-{
-	m_width = w;
-	m_height = h;
-
-	calcCameraIcons();
-
-	glViewport (0, 0, w, h);
-	glMatrixMode (GL_PROJECTION);
-	glLoadIdentity();
-	gluPerspective (45.0f, (double) w / (double) h, 1.0f, 10000.0f);
-	glMatrixMode (GL_MODELVIEW);
-}
-
-// =============================================================================
-//
-void GLRenderer::drawGLScene()
-{
-	if (document() == null)
-		return;
-
-	if (currentDocumentData().needZoomToFit)
-	{
-		currentDocumentData().needZoomToFit = false;
-		zoomAllToFit();
-	}
-
-	if (cfg::DrawWireframe and not isPicking())
-		glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
-
-	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-	glEnable (GL_DEPTH_TEST);
-
-	if (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 (camera() != EFrontCamera and 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 (camera() == 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);
-	}
-
-	glEnableClientState (GL_VERTEX_ARRAY);
-	glEnableClientState (GL_COLOR_ARRAY);
-
-	if (isPicking())
-	{
-		drawVBOs (VBOSF_Triangles, VBOCM_PickColors, GL_TRIANGLES);
-		drawVBOs (VBOSF_Quads, VBOCM_PickColors, GL_QUADS);
-		drawVBOs (VBOSF_Lines, VBOCM_PickColors, GL_LINES);
-		drawVBOs (VBOSF_CondLines, VBOCM_PickColors, GL_LINES);
-	}
-	else
-	{
-		if (cfg::BFCRedGreenView)
-		{
-			glEnable (GL_CULL_FACE);
-			glCullFace (GL_BACK);
-			drawVBOs (VBOSF_Triangles, VBOCM_BFCFrontColors, GL_TRIANGLES);
-			drawVBOs (VBOSF_Quads, VBOCM_BFCFrontColors, GL_QUADS);
-			glCullFace (GL_FRONT);
-			drawVBOs (VBOSF_Triangles, VBOCM_BFCBackColors, GL_TRIANGLES);
-			drawVBOs (VBOSF_Quads, VBOCM_BFCBackColors, GL_QUADS);
-			glDisable (GL_CULL_FACE);
-		}
-		else
-		{
-			if (cfg::RandomColors)
-			{
-				drawVBOs (VBOSF_Triangles, VBOCM_RandomColors, GL_TRIANGLES);
-				drawVBOs (VBOSF_Quads, VBOCM_RandomColors, GL_QUADS);
-			}
-			else
-			{
-				drawVBOs (VBOSF_Triangles, VBOCM_NormalColors, GL_TRIANGLES);
-				drawVBOs (VBOSF_Quads, VBOCM_NormalColors, GL_QUADS);
-			}
-		}
-
-		drawVBOs (VBOSF_Lines, VBOCM_NormalColors, GL_LINES);
-		glEnable (GL_LINE_STIPPLE);
-		drawVBOs (VBOSF_CondLines, VBOCM_NormalColors, GL_LINES);
-		glDisable (GL_LINE_STIPPLE);
-
-		if (cfg::DrawAxes)
-		{
-			glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO);
-			glVertexPointer (3, GL_FLOAT, 0, NULL);
-			glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO);
-			glColorPointer (3, GL_FLOAT, 0, NULL);
-			glDrawArrays (GL_LINES, 0, 6);
-			CHECK_GL_ERROR();
-		}
-	}
-
-	glPopMatrix();
-	glBindBuffer (GL_ARRAY_BUFFER, 0);
-	glDisableClientState (GL_VERTEX_ARRAY);
-	glDisableClientState (GL_COLOR_ARRAY);
-	CHECK_GL_ERROR();
-	glDisable (GL_CULL_FACE);
-	glMatrixMode (GL_MODELVIEW);
-	glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
-}
-
-// =============================================================================
-//
-void GLRenderer::drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type)
-{
-	// Filter this through some configuration options
-	if ((Eq (surface, VBOSF_Quads, VBOSF_Triangles) and cfg::DrawSurfaces == false) or
-		(surface == VBOSF_Lines and cfg::DrawEdgeLines == false) or
-		(surface == VBOSF_CondLines and cfg::DrawConditionalLines == false))
-	{
-		return;
-	}
-
-	int surfacenum = m_compiler->vboNumber (surface, VBOCM_Surfaces);
-	int colornum = m_compiler->vboNumber (surface, colors);
-	m_compiler->prepareVBO (surfacenum);
-	m_compiler->prepareVBO (colornum);
-	GLuint surfacevbo = m_compiler->vbo (surfacenum);
-	GLuint colorvbo = m_compiler->vbo (colornum);
-	GLsizei count = m_compiler->vboSize (surfacenum) / 3;
-
-	if (count > 0)
-	{
-		glBindBuffer (GL_ARRAY_BUFFER, surfacevbo);
-		glVertexPointer (3, GL_FLOAT, 0, null);
-		CHECK_GL_ERROR();
-		glBindBuffer (GL_ARRAY_BUFFER, colorvbo);
-		glColorPointer (4, GL_FLOAT, 0, null);
-		CHECK_GL_ERROR();
-		glDrawArrays (type, 0, count);
-		CHECK_GL_ERROR();
-	}
-}
-
-// =============================================================================
-// 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 LDFixedCamera* cam = &g_FixedCameras[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::Coordinate);
-		cy = Grid::Snap (cy, Grid::Coordinate);
-	}
-
-	cx *= negXFac;
-	cy *= negYFac;
-
-	RoundToDecimals (cx, 4);
-	RoundToDecimals (cy, 4);
-
-	// Create the vertex from the coordinates
-	pos3d.setCoordinate (axisX, cx);
-	pos3d.setCoordinate (axisY, cy);
-	pos3d.setCoordinate ((Axis) (3 - axisX - axisY), getDepthValue());
-	return pos3d;
-}
-
-// =============================================================================
-//
-// Inverse operation for the above - convert a 3D position to a 2D screen
-// position. Don't ask me how this code manages to work, I don't even know.
-//
-QPoint GLRenderer::coordconv3_2 (const Vertex& pos3d)
-{
-	GLfloat m[16];
-	const LDFixedCamera* cam = &g_FixedCameras[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.setX ((m[0] * x) + (m[1] * y) + (m[2] * z) + m[3]);
-	transformed.setY ((m[4] * x) + (m[5] * y) + (m[6] * z) + m[7]);
-	transformed.setZ ((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);
-}
-
-QPen GLRenderer::textPen() const
-{
-	return QPen (m_darkbg ? Qt::white : Qt::black);
-}
-
-QPen GLRenderer::linePen() const
-{
-	QPen linepen (m_thinBorderPen);
-	linepen.setWidth (2);
-	linepen.setColor (Luma (m_bgcolor) < 40 ? Qt::white : Qt::black);
-	return linepen;
-}
-
-// =============================================================================
-//
-void GLRenderer::paintEvent (QPaintEvent*)
-{
-	doMakeCurrent();
-	m_virtWidth = zoom();
-	m_virtHeight = (m_height * m_virtWidth) / m_width;
-	initGLData();
-	drawGLScene();
-
-	QPainter paint (this);
-	QFontMetrics metrics = QFontMetrics (QFont());
-	paint.setRenderHint (QPainter::HighQualityAntialiasing);
-
-	// If we wish to only draw the brick, stop here
-	if (isDrawOnly())
-		return;
-
-#ifndef RELEASE
-	if (not isPicking())
-	{
-		QString text = format ("Rotation: (%1, %2, %3)\nPanning: (%4, %5), Zoom: %6",
-			rot(X), rot(Y), rot(Z), pan(X), pan(Y), zoom());
-		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
-		paint.setPen (textPen());
-		paint.drawText ((width() - textSize.width()) / 2, height() - textSize.height(), textSize.width(),
-			textSize.height(), Qt::AlignCenter, text);
-	}
-#endif
-
-	if (camera() != EFreeCamera and not isPicking())
-	{
-		// Paint the overlay image if we have one
-		const LDGLOverlay& overlay = currentDocumentData().overlays[camera()];
-
-		if (overlay.img != null)
-		{
-			QPoint v0 = coordconv3_2 (currentDocumentData().overlays[camera()].v0),
-					   v1 = coordconv3_2 (currentDocumentData().overlays[camera()].v1);
-
-			QRect targRect (v0.x(), v0.y(), Abs (v1.x() - v0.x()), Abs (v1.y() - v0.y())),
-				  srcRect (0, 0, overlay.img->width(), overlay.img->height());
-			paint.drawImage (targRect, *overlay.img, srcRect);
-		}
-
-		// Paint the coordinates onto the screen.
-		QString text = format (tr ("X: %1, Y: %2, Z: %3"), m_position3D[X], m_position3D[Y], m_position3D[Z]);
-		QFontMetrics metrics = QFontMetrics (font());
-		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
-		paint.setPen (textPen());
-		paint.drawText (m_width - textSize.width(), m_height - 16, textSize.width(),
-			textSize.height(), Qt::AlignCenter, text);
-	}
-
-	if (not isPicking())
-	{
-		// Draw edit mode HUD
-		m_editmode->render (paint);
-
-		// 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 camera icons
-		for (CameraIcon& info : m_cameraIcons)
-		{
-			// Don't draw the free camera icon when in draw mode
-			if (&info == &m_cameraIcons[EFreeCamera] and not m_editmode->allowFreeCamera())
-				continue;
-
-			paint.drawPixmap (info.destRect, *info.img, info.srcRect);
-		}
-
-		QString formatstr = tr ("%1 Camera");
-
-		// Draw a label for the current camera in the bottom left corner
-		{
-			const int margin = 4;
-
-			QString label;
-			label = format (formatstr, tr (g_CameraNames[camera()]));
-			paint.setPen (textPen());
-			paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label);
-		}
-
-		// Tool tips
-		if (m_drawToolTip)
-		{
-			if (not m_cameraIcons[m_toolTipCamera].destRect.contains (m_mousePosition))
-				m_drawToolTip = false;
-			else
-			{
-				QString label = format (formatstr, tr (g_CameraNames[m_toolTipCamera]));
-				QToolTip::showText (m_globalpos, label);
-			}
-		}
-	}
-
-	// Message log
-	if (messageLog())
-	{
-		int y = 0;
-		const int margin = 2;
-		QColor penColor = textPen().color();
-
-		for (const MessageManager::Line& line : messageLog()->getLines())
-		{
-			penColor.setAlphaF (line.alpha);
-			paint.setPen (penColor);
-			paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text);
-			y += metrics.height();
-		}
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::drawBlip (QPainter& paint, QPointF pos) const
-{
-	QPen pen = m_thinBorderPen;
-	const int blipsize = 8;
-	pen.setWidth (1);
-	paint.setPen (pen);
-	paint.setBrush (QColor (64, 192, 0));
-	paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize);
-}
-
-// =============================================================================
-//
-void GLRenderer::clampAngle (double& angle) const
-{
-	while (angle < 0)
-		angle += 360.0;
-
-	while (angle > 360.0)
-		angle -= 360.0;
-}
-
-// =============================================================================
-//
-void GLRenderer::mouseReleaseEvent (QMouseEvent* ev)
-{
-	const bool wasLeft = (m_lastButtons & Qt::LeftButton) and not (ev->buttons() & Qt::LeftButton);
-
-	Qt::MouseButtons releasedbuttons = m_lastButtons & ~ev->buttons();
-
-	if (m_panning)
-		m_panning = false;
-
-	if (wasLeft)
-	{
-		// Check if we selected a camera icon
-		if (not mouseHasMoved())
-		{
-			for (CameraIcon & info : m_cameraIcons)
-			{
-				if (info.destRect.contains (ev->pos()))
-				{
-					setCamera (info.cam);
-					goto end;
-				}
-			}
-		}
-	}
-
-	if (not isDrawOnly())
-	{
-		AbstractEditMode::MouseEventData data;
-		data.ev = ev;
-		data.mouseMoved = mouseHasMoved();
-		data.keymods = m_keymods;
-		data.releasedButtons = releasedbuttons;
-
-		if (m_editmode->mouseReleased (data))
-			goto end;
-	}
-
-end:
-	update();
-	m_totalmove = 0;
-}
-
-// =============================================================================
-//
-void GLRenderer::mousePressEvent (QMouseEvent* ev)
-{
-	m_totalmove = 0;
-	m_lastButtons = ev->buttons();
-
-	if (m_editmode->mousePressed (ev))
-		ev->accept();
-}
-
-// =============================================================================
-//
-void GLRenderer::mouseMoveEvent (QMouseEvent* ev)
-{
-	int dx = ev->x() - m_mousePosition.x();
-	int dy = ev->y() - m_mousePosition.y();
-	m_totalmove += Abs (dx) + Abs (dy);
-	setCameraMoving (false);
-
-	if (not m_editmode->mouseMoved (ev))
-	{
-		const bool left = ev->buttons() & Qt::LeftButton,
-				mid = ev->buttons() & Qt::MidButton,
-				shift = ev->modifiers() & Qt::ShiftModifier;
-
-		if (mid or (left and shift))
-		{
-			pan (X) += 0.03f * dx * (zoom() / 7.5f);
-			pan (Y) -= 0.03f * dy * (zoom() / 7.5f);
-			m_panning = true;
-			setCameraMoving (true);
-		}
-		elif (left and camera() == EFreeCamera)
-		{
-			rot (X) = rot (X) + dy;
-			rot (Y) = rot (Y) + dx;
-
-			clampAngle (rot (X));
-			clampAngle (rot (Y));
-			setCameraMoving (true);
-		}
-	}
-
-	// Start the tool tip timer
-	if (not m_drawToolTip)
-		m_toolTipTimer->start (500);
-
-	// Update 2d position
-	m_mousePosition = ev->pos();
-	m_globalpos = ev->globalPos();
-
-#ifndef USE_QT5
-	m_mousePositionF = ev->posF();
-#else
-	m_mousePositionF = ev->localPos();
-#endif
-
-	// Calculate 3d position of the cursor
-	m_position3D = (camera() != EFreeCamera) ? coordconv2_3 (m_mousePosition, true) : Origin;
-
-	highlightCursorObject();
-	update();
-	ev->accept();
-}
-
-// =============================================================================
-//
-void GLRenderer::keyPressEvent (QKeyEvent* ev)
-{
-	m_keymods = ev->modifiers();
-}
-
-// =============================================================================
-//
-void GLRenderer::keyReleaseEvent (QKeyEvent* ev)
-{
-	m_keymods = ev->modifiers();
-	m_editmode->keyReleased (ev);
-	update();
-}
-
-// =============================================================================
-//
-void GLRenderer::wheelEvent (QWheelEvent* ev)
-{
-	doMakeCurrent();
-
-	zoomNotch (ev->delta() > 0);
-	zoom() = Clamp (zoom(), 0.01, 10000.0);
-	setCameraMoving (true);
-	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 ECamera cam)
-{
-	// The edit mode may forbid the free camera.
-	if (cam == EFreeCamera and not m_editmode->allowFreeCamera())
-		return;
-
-	m_camera = cam;
-	cfg::Camera = (int) cam;
-	g_win->updateEditModeActions();
-}
-
-// =============================================================================
-//
-void GLRenderer::pick (int mouseX, int mouseY, bool additive)
-{
-	pick (QRect (mouseX, mouseY, mouseX + 1, mouseY + 1), additive);
-}
-
-// =============================================================================
-//
-void GLRenderer::pick (QRect const& range, bool additive)
-{
-	doMakeCurrent();
-
-	// Clear the selection if we do not wish to add to it.
-	if (not additive)
-	{
-		LDObjectList oldsel = Selection();
-		CurrentDocument()->clearSelection();
-
-		for (LDObjectPtr obj : oldsel)
-			compileObject (obj);
-	}
-
-	// Paint the picking scene
-	setPicking (true);
-	drawGLScene();
-
-	int x0 = range.left();
-	int y0 = range.top();
-	int x1 = x0 + range.width();
-	int y1 = y0 + range.height();
-
-	// Clamp the values to ensure they're within bounds
-	x0 = Max (0, x0);
-	y0 = Max (0, y0);
-	x1 = Min (x1, m_width);
-	y1 = Min (y1, m_height);
-	const int areawidth = (x1 - x0);
-	const int areaheight = (y1 - y0);
-	const qint32 numpixels = areawidth * areaheight;
-
-	// Allocate space for the pixel data.
-	uchar* const pixeldata = new uchar[4 * numpixels];
-	uchar* pixelptr = &pixeldata[0];
-
-	// Read pixels from the color buffer.
-	glReadPixels (x0, m_height - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata);
-
-	LDObjectPtr removedObj;
-	QList<qint32> indices;
-
-	// Go through each pixel read and add them to the selection.
-	// Note: black is background, those indices are skipped.
-	for (qint32 i = 0; i < numpixels; ++i)
-	{
-		qint32 idx =
-			(*(pixelptr + 0) * 0x10000) +
-			(*(pixelptr + 1) * 0x100) +
-			*(pixelptr + 2);
-		pixelptr += 4;
-
-		if (idx != 0)
-			indices << idx;
-	}
-
-	RemoveDuplicates (indices);
-
-	for (qint32 idx : indices)
-	{
-		LDObjectPtr obj = LDObject::fromID (idx);
-		assert (obj != null);
-
-		// If this is an additive single pick and the object is currently selected,
-		// we remove it from selection instead.
-		if (additive)
-		{
-			if (obj->isSelected())
-			{
-				obj->deselect();
-				removedObj = obj;
-				break;
-			}
-		}
-
-		obj->select();
-	}
-
-	delete[] pixeldata;
-
-	// Update everything now.
-	g_win->updateSelection();
-
-	// Recompile the objects now to update their color
-	for (LDObjectPtr obj : Selection())
-		compileObject (obj);
-
-	if (removedObj)
-		compileObject (removedObj);
-
-	setPicking (false);
-	repaint();
-}
-
-//
-// Simpler version of GLRenderer::pick which simply picks whatever object on the screen
-//
-LDObjectPtr GLRenderer::pickOneObject (int mouseX, int mouseY)
-{
-	uchar pixel[4];
-	doMakeCurrent();
-	setPicking (true);
-	drawGLScene();
-	glReadPixels (mouseX, m_height - mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
-	LDObjectPtr obj = LDObject::fromID ((pixel[0] * 0x10000) + (pixel[1] * 0x100) + pixel[2]);
-	setPicking (false);
-	repaint();
-	return obj;
-}
-
-// =============================================================================
-//
-void GLRenderer::setEditMode (EditModeType a)
-{
-	if (m_editmode != null and m_editmode->type() == a)
-		return;
-
-	delete m_editmode;
-	m_editmode = AbstractEditMode::createByType (this, a);
-
-	// If we cannot use the free camera, use the top one instead.
-	if (camera() == EFreeCamera and not m_editmode->allowFreeCamera())
-		setCamera (ETopCamera);
-
-	g_win->updateEditModeActions();
-	update();
-}
-
-// =============================================================================
-//
-EditModeType GLRenderer::currentEditModeType() const
-{
-	return m_editmode->type();
-}
-
-// =============================================================================
-//
-void GLRenderer::setDocument (LDDocumentPtr const& a)
-{
-	m_document = a;
-
-	if (a != null)
-	{
-		initOverlaysFromObjects();
-
-		if (not currentDocumentData().init)
-		{
-			resetAllAngles();
-			currentDocumentData().init = true;
-		}
-
-		currentDocumentData().needZoomToFit = true;
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::setPicking (const bool& a)
-{
-	m_isPicking = a;
-	setBackground();
-
-	if (isPicking())
-	{
-		glDisable (GL_DITHER);
-
-		// Use particularly thick lines while picking ease up selecting lines.
-		glLineWidth (Max<double> (cfg::LineThickness, 6.5));
-	}
-	else
-	{
-		glEnable (GL_DITHER);
-
-		// Restore line thickness
-		glLineWidth (cfg::LineThickness);
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const
-{
-	const LDFixedCamera* cam = &g_FixedCameras[camera()];
-	relX = cam->axisX;
-	relY = cam->axisY;
-}
-
-// =============================================================================
-//
-Axis GLRenderer::getRelativeZ() const
-{
-	const LDFixedCamera* cam = &g_FixedCameras[camera()];
-	return (Axis) (3 - cam->axisX - cam->axisY);
-}
-
-// =============================================================================
-//
-static QList<Vertex> GetVerticesOf (LDObjectPtr obj)
-{
-	QList<Vertex> verts;
-
-	if (obj->numVertices() >= 2)
-	{
-		for (int i = 0; i < obj->numVertices(); ++i)
-			verts << obj->vertex (i);
-	}
-	elif (obj->type() == OBJ_Subfile)
-	{
-		LDSubfilePtr ref = obj.staticCast<LDSubfile>();
-		LDObjectList objs = ref->inlineContents (true, false);
-
-		for (LDObjectPtr obj : objs)
-		{
-			verts << GetVerticesOf (obj);
-			obj->destroy();
-		}
-	}
-
-	return verts;
-}
-
-// =============================================================================
-//
-void GLRenderer::compileObject (LDObjectPtr obj)
-{
-	compiler()->stageForCompilation (obj);
-}
-
-// =============================================================================
-//
-void GLRenderer::forgetObject (LDObjectPtr obj)
-{
-	if (compiler() != null)
-		compiler()->dropObject (obj);
-}
-
-// =============================================================================
-//
-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_mousePosition))
-		{
-			m_toolTipCamera = icon.cam;
-			m_drawToolTip = true;
-			update();
-			break;
-		}
-	}
-}
-
-// =============================================================================
-//
-Axis GLRenderer::getCameraAxis (bool y, ECamera camid)
-{
-	if (camid == (ECamera) -1)
-		camid = camera();
-
-	const LDFixedCamera* cam = &g_FixedCameras[camid];
-	return (y) ? cam->axisY : cam->axisX;
-}
-
-// =============================================================================
-//
-bool GLRenderer::setupOverlay (ECamera cam, QString file, int x, int y, int w, int h)
-{
-	QImage* img = new QImage (QImage (file).convertToFormat (QImage::Format_ARGB32));
-	LDGLOverlay& info = getOverlay (cam);
-
-	if (img->isNull())
-	{
-		Critical (tr ("Failed to load overlay image!"));
-		currentDocumentData().overlays[cam].invalid = true;
-		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;
-	info.invalid = false;
-
-	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 = Origin;
-	info.v0.setCoordinate (x2d, -(info.ox * info.lw * negXFac) / img->width());
-	info.v0.setCoordinate (y2d, (info.oy * info.lh * negYFac) / img->height());
-	info.v1.setCoordinate (x2d, info.v0[x2d] + info.lw);
-	info.v1.setCoordinate (y2d, info.v0[y2d] + info.lh);
-
-	// Set alpha of all pixels to 0.5
-	for (long i = 0; i < img->width(); ++i)
-	for (long j = 0; j < img->height(); ++j)
-	{
-		uint32 pixel = img->pixel (i, j);
-		img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF));
-	}
-
-	updateOverlayObjects();
-	return true;
-}
-
-// =============================================================================
-//
-void GLRenderer::clearOverlay()
-{
-	if (camera() == EFreeCamera)
-		return;
-
-	LDGLOverlay& info = currentDocumentData().overlays[camera()];
-	delete info.img;
-	info.img = null;
-
-	updateOverlayObjects();
-}
-
-// =============================================================================
-//
-void GLRenderer::setDepthValue (double depth)
-{
-	assert (camera() < EFreeCamera);
-	currentDocumentData().depthValues[camera()] = depth;
-}
-
-// =============================================================================
-//
-double GLRenderer::getDepthValue() const
-{
-	assert (camera() < EFreeCamera);
-	return currentDocumentData().depthValues[camera()];
-}
-
-// =============================================================================
-//
-const char* GLRenderer::getCameraName() const
-{
-	return g_CameraNames[camera()];
-}
-
-// =============================================================================
-//
-LDGLOverlay& GLRenderer::getOverlay (int newcam)
-{
-	return currentDocumentData().overlays[newcam];
-}
-
-// =============================================================================
-//
-void GLRenderer::zoomNotch (bool inward)
-{
-	zoom() *= inward ? 0.833f : 1.2f;
-}
-
-// =============================================================================
-//
-void GLRenderer::zoomToFit()
-{
-	zoom() = 30.0f;
-
-	if (document() == null or m_width == -1 or m_height == -1)
-		return;
-
-	bool lastfilled = false;
-	bool firstrun = true;
-	enum { black = 0xFF000000 };
-	bool inward = true;
-	int runaway = 50;
-
-	// Use the pick list while drawing the scene, this way we can tell whether borders
-	// are background or not.
-	setPicking (true);
-
-	while (--runaway)
-	{
-		if (zoom() > 10000.0 or zoom() < 0.0)
-		{
-			// Nothing to draw if we get here.
-			zoom() = 30.0;
-			break;
-		}
-
-		zoomNotch (inward);
-		QVector<unsigned char> capture (4 * m_width * m_height);
-		drawGLScene();
-		glReadPixels (0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, capture.data());
-		QImage image (capture.constData(), m_width, m_height, QImage::Format_ARGB32);
-		bool filled = false;
-
-		// Check the top and bottom rows
-		for (int i = 0; i < image.width(); ++i)
-		{
-			if (image.pixel (i, 0) != black or image.pixel (i, m_height - 1) != black)
-			{
-				filled = true;
-				break;
-			}
-		}
-
-		// Left and right edges
-		if (filled == false)
-		{
-			for (int i = 0; i < image.height(); ++i)
-			{
-				if (image.pixel (0, i) != black or image.pixel (m_width - 1, i) != black)
-				{
-					filled = true;
-					break;
-				}
-			}
-		}
-
-		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 = not 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 and not 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 (not filled and lastfilled)
-				break;
-
-			inward = not filled;
-		}
-
-		lastfilled = filled;
-	}
-
-	setPicking (false);
-}
-
-// =============================================================================
-//
-void GLRenderer::zoomAllToFit()
-{
-	zoomToFit();
-}
-
-// =============================================================================
-//
-void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev)
-{
-	if (m_editmode->mouseDoubleClicked (ev))
-		ev->accept();
-}
-
-// =============================================================================
-//
-LDOverlayPtr GLRenderer::findOverlayObject (ECamera cam)
-{
-	for (LDObjectPtr obj : document()->objects())
-	{
-		LDOverlayPtr ovlobj = obj.dynamicCast<LDOverlay>();
-
-		if (ovlobj != null and obj.staticCast<LDOverlay>()->camera() == cam)
-			return ovlobj;
-	}
-
-	return LDOverlayPtr();
-}
-
-// =============================================================================
-//
-// Read in overlays from the current file and update overlay info accordingly.
-//
-void GLRenderer::initOverlaysFromObjects()
-{
-	for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam)
-	{
-		if (cam == EFreeCamera)
-			continue;
-
-		LDGLOverlay& meta = currentDocumentData().overlays[cam];
-		LDOverlayPtr ovlobj = findOverlayObject (cam);
-
-		if (ovlobj == null and meta.img != null)
-		{
-			delete meta.img;
-			meta.img = null;
-		}
-		elif (ovlobj != null and
-			(meta.img == null or meta.fname != ovlobj->fileName()) and
-			not meta.invalid)
-		{
-			setupOverlay (cam, ovlobj->fileName(), ovlobj->x(),
-				ovlobj->y(), ovlobj->width(), ovlobj->height());
-		}
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::updateOverlayObjects()
-{
-	for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam)
-	{
-		if (cam == EFreeCamera)
-			continue;
-
-		LDGLOverlay& meta = currentDocumentData().overlays[cam];
-		LDOverlayPtr ovlobj = findOverlayObject (cam);
-
-		if (meta.img == null and ovlobj != null)
-		{
-			// If this is the last overlay image, we need to remove the empty space after it as well.
-			LDObjectPtr nextobj = ovlobj->next();
-
-			if (nextobj and nextobj->type() == OBJ_Empty)
-				nextobj->destroy();
-
-			// If the overlay object was there and the overlay itself is
-			// not, remove the object.
-			ovlobj->destroy();
-		}
-		elif (meta.img != null and ovlobj == null)
-		{
-			// Inverse case: image is there but the overlay object is
-			// not, thus create the object.
-			ovlobj = LDSpawn<LDOverlay>();
-
-			// Find a suitable position to place this object. We want to place
-			// this into the header, which is everything up to the first scemantic
-			// object. If we find another overlay object, place this object after
-			// the last one found. Otherwise, place it before the first schemantic
-			// object and put an empty object after it (though don't do this if
-			// there was no schemantic elements at all)
-			int i, lastOverlay = -1;
-			bool found = false;
-
-			for (i = 0; i < document()->getObjectCount(); ++i)
-			{
-				LDObjectPtr obj = document()->getObject (i);
-
-				if (obj->isScemantic())
-				{
-					found = true;
-					break;
-				}
-
-				if (obj->type() == OBJ_Overlay)
-					lastOverlay = i;
-			}
-
-			if (lastOverlay != -1)
-				document()->insertObj (lastOverlay + 1, ovlobj);
-			else
-			{
-				document()->insertObj (i, ovlobj);
-
-				if (found)
-					document()->insertObj (i + 1, LDSpawn<LDEmpty>());
-			}
-		}
-
-		if (meta.img != null and ovlobj != null)
-		{
-			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();
-}
-
-// =============================================================================
-//
-void GLRenderer::highlightCursorObject()
-{
-	if (not cfg::HighlightObjectBelowCursor and objectAtCursor() == null)
-		return;
-
-	LDObjectWeakPtr newObject;
-	LDObjectWeakPtr oldObject = objectAtCursor();
-	qint32 newIndex;
-
-	if (isCameraMoving() or not cfg::HighlightObjectBelowCursor)
-	{
-		newIndex = 0;
-	}
-	else
-	{
-		setPicking (true);
-		drawGLScene();
-		setPicking (false);
-
-		unsigned char pixel[4];
-		glReadPixels (m_mousePosition.x(), m_height - m_mousePosition.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel[0]);
-		newIndex = pixel[0] * 0x10000 | pixel[1] * 0x100 | pixel[2];
-	}
-
-	if (newIndex != (oldObject != null ? oldObject.toStrongRef()->id() : 0))
-	{
-		if (newIndex != 0)
-			newObject = LDObject::fromID (newIndex);
-
-		setObjectAtCursor (newObject);
-
-		if (oldObject != null)
-			compileObject (oldObject);
-
-		if (newObject != null)
-			compileObject (newObject);
-	}
-
-	update();
-}
-
-void GLRenderer::dragEnterEvent (QDragEnterEvent* ev)
-{
-	if (g_win != null and ev->source() == g_win->getPrimitivesTree() and g_win->getPrimitivesTree()->currentItem() != null)
-		ev->acceptProposedAction();
-}
-
-void GLRenderer::dropEvent (QDropEvent* ev)
-{
-	if (g_win != null and ev->source() == g_win->getPrimitivesTree())
-	{
-		QString primName = static_cast<SubfileListItem*> (g_win->getPrimitivesTree()->currentItem())->primitive()->name;
-		LDSubfilePtr ref = LDSpawn<LDSubfile>();
-		ref->setColor (MainColor());
-		ref->setFileInfo (GetDocument (primName));
-		ref->setPosition (Origin);
-		ref->setTransform (IdentityMatrix);
-		LDDocument::current()->insertObj (g_win->getInsertionPoint(), ref);
-		ref->select();
-		g_win->buildObjList();
-		g_win->R()->refresh();
-		ev->acceptProposedAction();
-	}
-}
-
-Vertex const& GLRenderer::position3D() const
-{
-	return m_position3D;
-}
-
-LDFixedCamera const& GLRenderer::getFixedCamera (ECamera cam) const
-{
-	return g_FixedCameras[cam];
-}
-
-bool GLRenderer::mouseHasMoved() const
-{
-	return m_totalmove >= 10;
-}
-
-QPoint const& GLRenderer::mousePosition() const
-{
-	return m_mousePosition;
-}
-
-QPointF const& GLRenderer::mousePositionF() const
-{
-	return m_mousePositionF;
-}
-
-void GLRenderer::doMakeCurrent()
-{
-	makeCurrent();
-	initializeOpenGLFunctions();
-}
-
-int GLRenderer::depthNegateFactor() const
-{
-	return g_FixedCameras[camera()].negatedDepth ? -1 : 1;
-}
-
-Qt::KeyboardModifiers GLRenderer::keyboardModifiers() const
-{
-	return m_keymods;
-}
-
-LDFixedCamera const& GetFixedCamera (ECamera cam)
-{
-	assert (cam != EFreeCamera);
-	return g_FixedCameras[cam];
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glRenderer.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,1642 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu 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/>.
+ */
+
+#define GL_GLEXT_PROTOTYPES
+#include <GL/glu.h>
+#include <GL/glext.h>
+#include <QGLWidget>
+#include <QWheelEvent>
+#include <QMouseEvent>
+#include <QContextMenuEvent>
+#include <QInputDialog>
+#include <QToolTip>
+#include <QTextDocument>
+#include <QTimer>
+#include <GL/glu.h>
+#include "main.h"
+#include "configuration.h"
+#include "ldDocument.h"
+#include "glRenderer.h"
+#include "colors.h"
+#include "mainWindow.h"
+#include "miscallenous.h"
+#include "editHistory.h"
+#include "dialogs.h"
+#include "addObjectDialog.h"
+#include "messageLog.h"
+#include "glCompiler.h"
+#include "primitives.h"
+
+const LDFixedCamera g_FixedCameras[6] =
+{
+	{{  1,  0, 0 }, X, Z, false, false, false }, // top
+	{{  0,  0, 0 }, X, Y, false,  true, false }, // front
+	{{  0,  1, 0 }, Z, Y,  true,  true, false }, // left
+	{{ -1,  0, 0 }, X, Z, false,  true, true }, // bottom
+	{{  0,  0, 0 }, X, Y,  true,  true, true }, // back
+	{{  0, -1, 0 }, Z, Y, false,  true, true }, // right
+};
+
+CFGENTRY (String, BackgroundColor,			"#FFFFFF")
+CFGENTRY (String, MainColor,					"#A0A0A0")
+CFGENTRY (Float, MainColorAlpha,				1.0)
+CFGENTRY (Int, LineThickness,				2)
+CFGENTRY (Bool, BFCRedGreenView,			false)
+CFGENTRY (Int, Camera,						EFreeCamera)
+CFGENTRY (Bool, BlackEdges,					false)
+CFGENTRY (Bool, DrawAxes,					false)
+CFGENTRY (Bool, DrawWireframe,				false)
+CFGENTRY (Bool, UseLogoStuds,				false)
+CFGENTRY (Bool, AntiAliasedLines,			true)
+CFGENTRY (Bool, RandomColors,				false)
+CFGENTRY (Bool, HighlightObjectBelowCursor,	true)
+CFGENTRY (Bool, DrawSurfaces,				true)
+CFGENTRY (Bool, DrawEdgeLines,				true)
+CFGENTRY (Bool, DrawConditionalLines,		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")
+};
+
+struct LDGLAxis
+{
+	const QColor col;
+	const Vertex vert;
+};
+
+// Definitions for visual axes, drawn on the screen
+static const LDGLAxis g_GLAxes[3] =
+{
+	{ QColor (192,  96,  96), Vertex (10000, 0, 0) }, // X
+	{ QColor (48,  192,  48), Vertex (0, 10000, 0) }, // Y
+	{ QColor (48,  112, 192), Vertex (0, 0, 10000) }, // Z
+};
+
+static bool RendererInitialized (false);
+
+// =============================================================================
+//
+GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent)
+{
+	m_isPicking = false;
+	m_camera = (ECamera) cfg::Camera;
+	m_drawToolTip = false;
+	m_editmode = AbstractEditMode::createByType (this, EditModeType::Select);
+	m_panning = false;
+	m_compiler = new GLCompiler (this);
+	setDrawOnly (false);
+	setMessageLog (null);
+	m_width = m_height = -1;
+	m_position3D = Origin;
+	m_toolTipTimer = new QTimer (this);
+	m_toolTipTimer->setSingleShot (true);
+	m_isCameraMoving = false;
+	m_thinBorderPen = QPen (QColor (0, 0, 0, 208), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
+	m_thinBorderPen.setWidth (1);
+	setAcceptDrops (true);
+	connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer()));
+
+	// Init camera icons
+	for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam)
+	{
+		QString iconname = format ("camera-%1", tr (g_CameraNames[cam]).toLower());
+		CameraIcon* info = &m_cameraIcons[cam];
+		info->img = new QPixmap (GetIcon (iconname));
+		info->cam = cam;
+	}
+
+	calcCameraIcons();
+}
+
+// =============================================================================
+//
+GLRenderer::~GLRenderer()
+{
+	for (int i = 0; i < 6; ++i)
+		delete currentDocumentData().overlays[i].img;
+
+	for (CameraIcon& info : m_cameraIcons)
+		delete info.img;
+
+	if (messageLog())
+		messageLog()->setRenderer (null);
+
+	m_compiler->setRenderer (null);
+	delete m_compiler;
+	delete m_editmode;
+
+	glDeleteBuffers (1, &m_axesVBO);
+	glDeleteBuffers (1, &m_axesColorVBO);
+}
+
+// =============================================================================
+// 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 (cfg::AntiAliasedLines)
+	{
+		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::needZoomToFit()
+{
+	if (document() != null)
+		currentDocumentData().needZoomToFit = true;
+}
+
+// =============================================================================
+//
+void GLRenderer::resetAngles()
+{
+	rot (X) = 30.0f;
+	rot (Y) = 325.f;
+	pan (X) = pan (Y) = rot (Z) = 0.0f;
+	needZoomToFit();
+}
+
+// =============================================================================
+//
+void GLRenderer::resetAllAngles()
+{
+	ECamera oldcam = camera();
+
+	for (int i = 0; i < 7; ++i)
+	{
+		setCamera ((ECamera) i);
+		resetAngles();
+	}
+
+	setCamera (oldcam);
+}
+
+// =============================================================================
+//
+void GLRenderer::initializeGL()
+{
+#ifdef USE_QT5
+	initializeOpenGLFunctions();
+#endif
+	setBackground();
+	glLineWidth (cfg::LineThickness);
+	glLineStipple (1, 0x6666);
+	setAutoFillBackground (false);
+	setMouseTracking (true);
+	setFocusPolicy (Qt::WheelFocus);
+	compiler()->initialize();
+	initializeAxes();
+	RendererInitialized = true;
+}
+
+// =============================================================================
+//
+void GLRenderer::initializeAxes()
+{
+	float axesdata[18];
+	float colordata[18];
+	memset (axesdata, 0, sizeof axesdata);
+
+	for (int i = 0; i < 3; ++i)
+	{
+		for_axes (ax)
+		{
+			axesdata[(i * 6) + ax] = g_GLAxes[i].vert[ax];
+			axesdata[(i * 6) + 3 + ax] = -g_GLAxes[i].vert[ax];
+		}
+
+		for (int j = 0; j < 2; ++j)
+		{
+			colordata[(i * 6) + (j * 3) + 0] = g_GLAxes[i].col.red();
+			colordata[(i * 6) + (j * 3) + 1] = g_GLAxes[i].col.green();
+			colordata[(i * 6) + (j * 3) + 2] = g_GLAxes[i].col.blue();
+		}
+	}
+
+	glGenBuffers (1, &m_axesVBO);
+	glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO);
+	glBufferData (GL_ARRAY_BUFFER, sizeof axesdata, axesdata, GL_STATIC_DRAW);
+	glGenBuffers (1, &m_axesColorVBO);
+	glBindBuffer (GL_ARRAY_BUFFER, m_axesColorVBO);
+	glBufferData (GL_ARRAY_BUFFER, sizeof colordata, colordata, GL_STATIC_DRAW);
+	glBindBuffer (GL_ARRAY_BUFFER, 0);
+}
+
+// =============================================================================
+//
+QColor GLRenderer::getMainColor()
+{
+	QColor col (cfg::MainColor);
+
+	if (not col.isValid())
+		return QColor (0, 0, 0);
+
+	col.setAlpha (cfg::MainColorAlpha * 255.f);
+	return col;
+}
+
+// =============================================================================
+//
+void GLRenderer::setBackground()
+{
+	if (isPicking())
+	{
+		glClearColor (0.0f, 0.0f, 0.0f, 1.0f);
+		return;
+	}
+
+	QColor col (cfg::BackgroundColor);
+
+	if (not col.isValid())
+		return;
+
+	col.setAlpha (255);
+
+	m_darkbg = Luma (col) < 80;
+	m_bgcolor = col;
+	qglClearColor (col);
+}
+
+// =============================================================================
+//
+void GLRenderer::refresh()
+{
+	update();
+
+	if (isVisible())
+		swapBuffers();
+}
+
+// =============================================================================
+//
+void GLRenderer::hardRefresh()
+{
+	if (not RendererInitialized)
+		return;
+
+	compiler()->compileDocument (CurrentDocument());
+	refresh();
+}
+
+// =============================================================================
+//
+void GLRenderer::resizeGL (int w, int h)
+{
+	m_width = w;
+	m_height = h;
+
+	calcCameraIcons();
+
+	glViewport (0, 0, w, h);
+	glMatrixMode (GL_PROJECTION);
+	glLoadIdentity();
+	gluPerspective (45.0f, (double) w / (double) h, 1.0f, 10000.0f);
+	glMatrixMode (GL_MODELVIEW);
+}
+
+// =============================================================================
+//
+void GLRenderer::drawGLScene()
+{
+	if (document() == null)
+		return;
+
+	if (currentDocumentData().needZoomToFit)
+	{
+		currentDocumentData().needZoomToFit = false;
+		zoomAllToFit();
+	}
+
+	if (cfg::DrawWireframe and not isPicking())
+		glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
+
+	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	glEnable (GL_DEPTH_TEST);
+
+	if (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 (camera() != EFrontCamera and 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 (camera() == 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);
+	}
+
+	glEnableClientState (GL_VERTEX_ARRAY);
+	glEnableClientState (GL_COLOR_ARRAY);
+
+	if (isPicking())
+	{
+		drawVBOs (VBOSF_Triangles, VBOCM_PickColors, GL_TRIANGLES);
+		drawVBOs (VBOSF_Quads, VBOCM_PickColors, GL_QUADS);
+		drawVBOs (VBOSF_Lines, VBOCM_PickColors, GL_LINES);
+		drawVBOs (VBOSF_CondLines, VBOCM_PickColors, GL_LINES);
+	}
+	else
+	{
+		if (cfg::BFCRedGreenView)
+		{
+			glEnable (GL_CULL_FACE);
+			glCullFace (GL_BACK);
+			drawVBOs (VBOSF_Triangles, VBOCM_BFCFrontColors, GL_TRIANGLES);
+			drawVBOs (VBOSF_Quads, VBOCM_BFCFrontColors, GL_QUADS);
+			glCullFace (GL_FRONT);
+			drawVBOs (VBOSF_Triangles, VBOCM_BFCBackColors, GL_TRIANGLES);
+			drawVBOs (VBOSF_Quads, VBOCM_BFCBackColors, GL_QUADS);
+			glDisable (GL_CULL_FACE);
+		}
+		else
+		{
+			if (cfg::RandomColors)
+			{
+				drawVBOs (VBOSF_Triangles, VBOCM_RandomColors, GL_TRIANGLES);
+				drawVBOs (VBOSF_Quads, VBOCM_RandomColors, GL_QUADS);
+			}
+			else
+			{
+				drawVBOs (VBOSF_Triangles, VBOCM_NormalColors, GL_TRIANGLES);
+				drawVBOs (VBOSF_Quads, VBOCM_NormalColors, GL_QUADS);
+			}
+		}
+
+		drawVBOs (VBOSF_Lines, VBOCM_NormalColors, GL_LINES);
+		glEnable (GL_LINE_STIPPLE);
+		drawVBOs (VBOSF_CondLines, VBOCM_NormalColors, GL_LINES);
+		glDisable (GL_LINE_STIPPLE);
+
+		if (cfg::DrawAxes)
+		{
+			glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO);
+			glVertexPointer (3, GL_FLOAT, 0, NULL);
+			glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO);
+			glColorPointer (3, GL_FLOAT, 0, NULL);
+			glDrawArrays (GL_LINES, 0, 6);
+			CHECK_GL_ERROR();
+		}
+	}
+
+	glPopMatrix();
+	glBindBuffer (GL_ARRAY_BUFFER, 0);
+	glDisableClientState (GL_VERTEX_ARRAY);
+	glDisableClientState (GL_COLOR_ARRAY);
+	CHECK_GL_ERROR();
+	glDisable (GL_CULL_FACE);
+	glMatrixMode (GL_MODELVIEW);
+	glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
+}
+
+// =============================================================================
+//
+void GLRenderer::drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type)
+{
+	// Filter this through some configuration options
+	if ((Eq (surface, VBOSF_Quads, VBOSF_Triangles) and cfg::DrawSurfaces == false) or
+		(surface == VBOSF_Lines and cfg::DrawEdgeLines == false) or
+		(surface == VBOSF_CondLines and cfg::DrawConditionalLines == false))
+	{
+		return;
+	}
+
+	int surfacenum = m_compiler->vboNumber (surface, VBOCM_Surfaces);
+	int colornum = m_compiler->vboNumber (surface, colors);
+	m_compiler->prepareVBO (surfacenum);
+	m_compiler->prepareVBO (colornum);
+	GLuint surfacevbo = m_compiler->vbo (surfacenum);
+	GLuint colorvbo = m_compiler->vbo (colornum);
+	GLsizei count = m_compiler->vboSize (surfacenum) / 3;
+
+	if (count > 0)
+	{
+		glBindBuffer (GL_ARRAY_BUFFER, surfacevbo);
+		glVertexPointer (3, GL_FLOAT, 0, null);
+		CHECK_GL_ERROR();
+		glBindBuffer (GL_ARRAY_BUFFER, colorvbo);
+		glColorPointer (4, GL_FLOAT, 0, null);
+		CHECK_GL_ERROR();
+		glDrawArrays (type, 0, count);
+		CHECK_GL_ERROR();
+	}
+}
+
+// =============================================================================
+// 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 LDFixedCamera* cam = &g_FixedCameras[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::Coordinate);
+		cy = Grid::Snap (cy, Grid::Coordinate);
+	}
+
+	cx *= negXFac;
+	cy *= negYFac;
+
+	RoundToDecimals (cx, 4);
+	RoundToDecimals (cy, 4);
+
+	// Create the vertex from the coordinates
+	pos3d.setCoordinate (axisX, cx);
+	pos3d.setCoordinate (axisY, cy);
+	pos3d.setCoordinate ((Axis) (3 - axisX - axisY), getDepthValue());
+	return pos3d;
+}
+
+// =============================================================================
+//
+// Inverse operation for the above - convert a 3D position to a 2D screen
+// position. Don't ask me how this code manages to work, I don't even know.
+//
+QPoint GLRenderer::coordconv3_2 (const Vertex& pos3d)
+{
+	GLfloat m[16];
+	const LDFixedCamera* cam = &g_FixedCameras[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.setX ((m[0] * x) + (m[1] * y) + (m[2] * z) + m[3]);
+	transformed.setY ((m[4] * x) + (m[5] * y) + (m[6] * z) + m[7]);
+	transformed.setZ ((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);
+}
+
+QPen GLRenderer::textPen() const
+{
+	return QPen (m_darkbg ? Qt::white : Qt::black);
+}
+
+QPen GLRenderer::linePen() const
+{
+	QPen linepen (m_thinBorderPen);
+	linepen.setWidth (2);
+	linepen.setColor (Luma (m_bgcolor) < 40 ? Qt::white : Qt::black);
+	return linepen;
+}
+
+// =============================================================================
+//
+void GLRenderer::paintEvent (QPaintEvent*)
+{
+	doMakeCurrent();
+	m_virtWidth = zoom();
+	m_virtHeight = (m_height * m_virtWidth) / m_width;
+	initGLData();
+	drawGLScene();
+
+	QPainter paint (this);
+	QFontMetrics metrics = QFontMetrics (QFont());
+	paint.setRenderHint (QPainter::HighQualityAntialiasing);
+
+	// If we wish to only draw the brick, stop here
+	if (isDrawOnly())
+		return;
+
+#ifndef RELEASE
+	if (not isPicking())
+	{
+		QString text = format ("Rotation: (%1, %2, %3)\nPanning: (%4, %5), Zoom: %6",
+			rot(X), rot(Y), rot(Z), pan(X), pan(Y), zoom());
+		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
+		paint.setPen (textPen());
+		paint.drawText ((width() - textSize.width()) / 2, height() - textSize.height(), textSize.width(),
+			textSize.height(), Qt::AlignCenter, text);
+	}
+#endif
+
+	if (camera() != EFreeCamera and not isPicking())
+	{
+		// Paint the overlay image if we have one
+		const LDGLOverlay& overlay = currentDocumentData().overlays[camera()];
+
+		if (overlay.img != null)
+		{
+			QPoint v0 = coordconv3_2 (currentDocumentData().overlays[camera()].v0),
+					   v1 = coordconv3_2 (currentDocumentData().overlays[camera()].v1);
+
+			QRect targRect (v0.x(), v0.y(), Abs (v1.x() - v0.x()), Abs (v1.y() - v0.y())),
+				  srcRect (0, 0, overlay.img->width(), overlay.img->height());
+			paint.drawImage (targRect, *overlay.img, srcRect);
+		}
+
+		// Paint the coordinates onto the screen.
+		QString text = format (tr ("X: %1, Y: %2, Z: %3"), m_position3D[X], m_position3D[Y], m_position3D[Z]);
+		QFontMetrics metrics = QFontMetrics (font());
+		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
+		paint.setPen (textPen());
+		paint.drawText (m_width - textSize.width(), m_height - 16, textSize.width(),
+			textSize.height(), Qt::AlignCenter, text);
+	}
+
+	if (not isPicking())
+	{
+		// Draw edit mode HUD
+		m_editmode->render (paint);
+
+		// 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 camera icons
+		for (CameraIcon& info : m_cameraIcons)
+		{
+			// Don't draw the free camera icon when in draw mode
+			if (&info == &m_cameraIcons[EFreeCamera] and not m_editmode->allowFreeCamera())
+				continue;
+
+			paint.drawPixmap (info.destRect, *info.img, info.srcRect);
+		}
+
+		QString formatstr = tr ("%1 Camera");
+
+		// Draw a label for the current camera in the bottom left corner
+		{
+			const int margin = 4;
+
+			QString label;
+			label = format (formatstr, tr (g_CameraNames[camera()]));
+			paint.setPen (textPen());
+			paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label);
+		}
+
+		// Tool tips
+		if (m_drawToolTip)
+		{
+			if (not m_cameraIcons[m_toolTipCamera].destRect.contains (m_mousePosition))
+				m_drawToolTip = false;
+			else
+			{
+				QString label = format (formatstr, tr (g_CameraNames[m_toolTipCamera]));
+				QToolTip::showText (m_globalpos, label);
+			}
+		}
+	}
+
+	// Message log
+	if (messageLog())
+	{
+		int y = 0;
+		const int margin = 2;
+		QColor penColor = textPen().color();
+
+		for (const MessageManager::Line& line : messageLog()->getLines())
+		{
+			penColor.setAlphaF (line.alpha);
+			paint.setPen (penColor);
+			paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text);
+			y += metrics.height();
+		}
+	}
+}
+
+// =============================================================================
+//
+void GLRenderer::drawBlip (QPainter& paint, QPointF pos) const
+{
+	QPen pen = m_thinBorderPen;
+	const int blipsize = 8;
+	pen.setWidth (1);
+	paint.setPen (pen);
+	paint.setBrush (QColor (64, 192, 0));
+	paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize);
+}
+
+// =============================================================================
+//
+void GLRenderer::clampAngle (double& angle) const
+{
+	while (angle < 0)
+		angle += 360.0;
+
+	while (angle > 360.0)
+		angle -= 360.0;
+}
+
+// =============================================================================
+//
+void GLRenderer::mouseReleaseEvent (QMouseEvent* ev)
+{
+	const bool wasLeft = (m_lastButtons & Qt::LeftButton) and not (ev->buttons() & Qt::LeftButton);
+
+	Qt::MouseButtons releasedbuttons = m_lastButtons & ~ev->buttons();
+
+	if (m_panning)
+		m_panning = false;
+
+	if (wasLeft)
+	{
+		// Check if we selected a camera icon
+		if (not mouseHasMoved())
+		{
+			for (CameraIcon & info : m_cameraIcons)
+			{
+				if (info.destRect.contains (ev->pos()))
+				{
+					setCamera (info.cam);
+					goto end;
+				}
+			}
+		}
+	}
+
+	if (not isDrawOnly())
+	{
+		AbstractEditMode::MouseEventData data;
+		data.ev = ev;
+		data.mouseMoved = mouseHasMoved();
+		data.keymods = m_keymods;
+		data.releasedButtons = releasedbuttons;
+
+		if (m_editmode->mouseReleased (data))
+			goto end;
+	}
+
+end:
+	update();
+	m_totalmove = 0;
+}
+
+// =============================================================================
+//
+void GLRenderer::mousePressEvent (QMouseEvent* ev)
+{
+	m_totalmove = 0;
+	m_lastButtons = ev->buttons();
+
+	if (m_editmode->mousePressed (ev))
+		ev->accept();
+}
+
+// =============================================================================
+//
+void GLRenderer::mouseMoveEvent (QMouseEvent* ev)
+{
+	int dx = ev->x() - m_mousePosition.x();
+	int dy = ev->y() - m_mousePosition.y();
+	m_totalmove += Abs (dx) + Abs (dy);
+	setCameraMoving (false);
+
+	if (not m_editmode->mouseMoved (ev))
+	{
+		const bool left = ev->buttons() & Qt::LeftButton,
+				mid = ev->buttons() & Qt::MidButton,
+				shift = ev->modifiers() & Qt::ShiftModifier;
+
+		if (mid or (left and shift))
+		{
+			pan (X) += 0.03f * dx * (zoom() / 7.5f);
+			pan (Y) -= 0.03f * dy * (zoom() / 7.5f);
+			m_panning = true;
+			setCameraMoving (true);
+		}
+		elif (left and camera() == EFreeCamera)
+		{
+			rot (X) = rot (X) + dy;
+			rot (Y) = rot (Y) + dx;
+
+			clampAngle (rot (X));
+			clampAngle (rot (Y));
+			setCameraMoving (true);
+		}
+	}
+
+	// Start the tool tip timer
+	if (not m_drawToolTip)
+		m_toolTipTimer->start (500);
+
+	// Update 2d position
+	m_mousePosition = ev->pos();
+	m_globalpos = ev->globalPos();
+
+#ifndef USE_QT5
+	m_mousePositionF = ev->posF();
+#else
+	m_mousePositionF = ev->localPos();
+#endif
+
+	// Calculate 3d position of the cursor
+	m_position3D = (camera() != EFreeCamera) ? coordconv2_3 (m_mousePosition, true) : Origin;
+
+	highlightCursorObject();
+	update();
+	ev->accept();
+}
+
+// =============================================================================
+//
+void GLRenderer::keyPressEvent (QKeyEvent* ev)
+{
+	m_keymods = ev->modifiers();
+}
+
+// =============================================================================
+//
+void GLRenderer::keyReleaseEvent (QKeyEvent* ev)
+{
+	m_keymods = ev->modifiers();
+	m_editmode->keyReleased (ev);
+	update();
+}
+
+// =============================================================================
+//
+void GLRenderer::wheelEvent (QWheelEvent* ev)
+{
+	doMakeCurrent();
+
+	zoomNotch (ev->delta() > 0);
+	zoom() = Clamp (zoom(), 0.01, 10000.0);
+	setCameraMoving (true);
+	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 ECamera cam)
+{
+	// The edit mode may forbid the free camera.
+	if (cam == EFreeCamera and not m_editmode->allowFreeCamera())
+		return;
+
+	m_camera = cam;
+	cfg::Camera = (int) cam;
+	g_win->updateEditModeActions();
+}
+
+// =============================================================================
+//
+void GLRenderer::pick (int mouseX, int mouseY, bool additive)
+{
+	pick (QRect (mouseX, mouseY, mouseX + 1, mouseY + 1), additive);
+}
+
+// =============================================================================
+//
+void GLRenderer::pick (QRect const& range, bool additive)
+{
+	doMakeCurrent();
+
+	// Clear the selection if we do not wish to add to it.
+	if (not additive)
+	{
+		LDObjectList oldsel = Selection();
+		CurrentDocument()->clearSelection();
+
+		for (LDObjectPtr obj : oldsel)
+			compileObject (obj);
+	}
+
+	// Paint the picking scene
+	setPicking (true);
+	drawGLScene();
+
+	int x0 = range.left();
+	int y0 = range.top();
+	int x1 = x0 + range.width();
+	int y1 = y0 + range.height();
+
+	// Clamp the values to ensure they're within bounds
+	x0 = Max (0, x0);
+	y0 = Max (0, y0);
+	x1 = Min (x1, m_width);
+	y1 = Min (y1, m_height);
+	const int areawidth = (x1 - x0);
+	const int areaheight = (y1 - y0);
+	const qint32 numpixels = areawidth * areaheight;
+
+	// Allocate space for the pixel data.
+	uchar* const pixeldata = new uchar[4 * numpixels];
+	uchar* pixelptr = &pixeldata[0];
+
+	// Read pixels from the color buffer.
+	glReadPixels (x0, m_height - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata);
+
+	LDObjectPtr removedObj;
+	QList<qint32> indices;
+
+	// Go through each pixel read and add them to the selection.
+	// Note: black is background, those indices are skipped.
+	for (qint32 i = 0; i < numpixels; ++i)
+	{
+		qint32 idx =
+			(*(pixelptr + 0) * 0x10000) +
+			(*(pixelptr + 1) * 0x100) +
+			*(pixelptr + 2);
+		pixelptr += 4;
+
+		if (idx != 0)
+			indices << idx;
+	}
+
+	RemoveDuplicates (indices);
+
+	for (qint32 idx : indices)
+	{
+		LDObjectPtr obj = LDObject::fromID (idx);
+		assert (obj != null);
+
+		// If this is an additive single pick and the object is currently selected,
+		// we remove it from selection instead.
+		if (additive)
+		{
+			if (obj->isSelected())
+			{
+				obj->deselect();
+				removedObj = obj;
+				break;
+			}
+		}
+
+		obj->select();
+	}
+
+	delete[] pixeldata;
+
+	// Update everything now.
+	g_win->updateSelection();
+
+	// Recompile the objects now to update their color
+	for (LDObjectPtr obj : Selection())
+		compileObject (obj);
+
+	if (removedObj)
+		compileObject (removedObj);
+
+	setPicking (false);
+	repaint();
+}
+
+//
+// Simpler version of GLRenderer::pick which simply picks whatever object on the screen
+//
+LDObjectPtr GLRenderer::pickOneObject (int mouseX, int mouseY)
+{
+	uchar pixel[4];
+	doMakeCurrent();
+	setPicking (true);
+	drawGLScene();
+	glReadPixels (mouseX, m_height - mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
+	LDObjectPtr obj = LDObject::fromID ((pixel[0] * 0x10000) + (pixel[1] * 0x100) + pixel[2]);
+	setPicking (false);
+	repaint();
+	return obj;
+}
+
+// =============================================================================
+//
+void GLRenderer::setEditMode (EditModeType a)
+{
+	if (m_editmode != null and m_editmode->type() == a)
+		return;
+
+	delete m_editmode;
+	m_editmode = AbstractEditMode::createByType (this, a);
+
+	// If we cannot use the free camera, use the top one instead.
+	if (camera() == EFreeCamera and not m_editmode->allowFreeCamera())
+		setCamera (ETopCamera);
+
+	g_win->updateEditModeActions();
+	update();
+}
+
+// =============================================================================
+//
+EditModeType GLRenderer::currentEditModeType() const
+{
+	return m_editmode->type();
+}
+
+// =============================================================================
+//
+void GLRenderer::setDocument (LDDocumentPtr const& a)
+{
+	m_document = a;
+
+	if (a != null)
+	{
+		initOverlaysFromObjects();
+
+		if (not currentDocumentData().init)
+		{
+			resetAllAngles();
+			currentDocumentData().init = true;
+		}
+
+		currentDocumentData().needZoomToFit = true;
+	}
+}
+
+// =============================================================================
+//
+void GLRenderer::setPicking (const bool& a)
+{
+	m_isPicking = a;
+	setBackground();
+
+	if (isPicking())
+	{
+		glDisable (GL_DITHER);
+
+		// Use particularly thick lines while picking ease up selecting lines.
+		glLineWidth (Max<double> (cfg::LineThickness, 6.5));
+	}
+	else
+	{
+		glEnable (GL_DITHER);
+
+		// Restore line thickness
+		glLineWidth (cfg::LineThickness);
+	}
+}
+
+// =============================================================================
+//
+void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const
+{
+	const LDFixedCamera* cam = &g_FixedCameras[camera()];
+	relX = cam->axisX;
+	relY = cam->axisY;
+}
+
+// =============================================================================
+//
+Axis GLRenderer::getRelativeZ() const
+{
+	const LDFixedCamera* cam = &g_FixedCameras[camera()];
+	return (Axis) (3 - cam->axisX - cam->axisY);
+}
+
+// =============================================================================
+//
+static QList<Vertex> GetVerticesOf (LDObjectPtr obj)
+{
+	QList<Vertex> verts;
+
+	if (obj->numVertices() >= 2)
+	{
+		for (int i = 0; i < obj->numVertices(); ++i)
+			verts << obj->vertex (i);
+	}
+	elif (obj->type() == OBJ_Subfile)
+	{
+		LDSubfilePtr ref = obj.staticCast<LDSubfile>();
+		LDObjectList objs = ref->inlineContents (true, false);
+
+		for (LDObjectPtr obj : objs)
+		{
+			verts << GetVerticesOf (obj);
+			obj->destroy();
+		}
+	}
+
+	return verts;
+}
+
+// =============================================================================
+//
+void GLRenderer::compileObject (LDObjectPtr obj)
+{
+	compiler()->stageForCompilation (obj);
+}
+
+// =============================================================================
+//
+void GLRenderer::forgetObject (LDObjectPtr obj)
+{
+	if (compiler() != null)
+		compiler()->dropObject (obj);
+}
+
+// =============================================================================
+//
+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_mousePosition))
+		{
+			m_toolTipCamera = icon.cam;
+			m_drawToolTip = true;
+			update();
+			break;
+		}
+	}
+}
+
+// =============================================================================
+//
+Axis GLRenderer::getCameraAxis (bool y, ECamera camid)
+{
+	if (camid == (ECamera) -1)
+		camid = camera();
+
+	const LDFixedCamera* cam = &g_FixedCameras[camid];
+	return (y) ? cam->axisY : cam->axisX;
+}
+
+// =============================================================================
+//
+bool GLRenderer::setupOverlay (ECamera cam, QString file, int x, int y, int w, int h)
+{
+	QImage* img = new QImage (QImage (file).convertToFormat (QImage::Format_ARGB32));
+	LDGLOverlay& info = getOverlay (cam);
+
+	if (img->isNull())
+	{
+		Critical (tr ("Failed to load overlay image!"));
+		currentDocumentData().overlays[cam].invalid = true;
+		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;
+	info.invalid = false;
+
+	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 = Origin;
+	info.v0.setCoordinate (x2d, -(info.ox * info.lw * negXFac) / img->width());
+	info.v0.setCoordinate (y2d, (info.oy * info.lh * negYFac) / img->height());
+	info.v1.setCoordinate (x2d, info.v0[x2d] + info.lw);
+	info.v1.setCoordinate (y2d, info.v0[y2d] + info.lh);
+
+	// Set alpha of all pixels to 0.5
+	for (long i = 0; i < img->width(); ++i)
+	for (long j = 0; j < img->height(); ++j)
+	{
+		uint32 pixel = img->pixel (i, j);
+		img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF));
+	}
+
+	updateOverlayObjects();
+	return true;
+}
+
+// =============================================================================
+//
+void GLRenderer::clearOverlay()
+{
+	if (camera() == EFreeCamera)
+		return;
+
+	LDGLOverlay& info = currentDocumentData().overlays[camera()];
+	delete info.img;
+	info.img = null;
+
+	updateOverlayObjects();
+}
+
+// =============================================================================
+//
+void GLRenderer::setDepthValue (double depth)
+{
+	assert (camera() < EFreeCamera);
+	currentDocumentData().depthValues[camera()] = depth;
+}
+
+// =============================================================================
+//
+double GLRenderer::getDepthValue() const
+{
+	assert (camera() < EFreeCamera);
+	return currentDocumentData().depthValues[camera()];
+}
+
+// =============================================================================
+//
+const char* GLRenderer::getCameraName() const
+{
+	return g_CameraNames[camera()];
+}
+
+// =============================================================================
+//
+LDGLOverlay& GLRenderer::getOverlay (int newcam)
+{
+	return currentDocumentData().overlays[newcam];
+}
+
+// =============================================================================
+//
+void GLRenderer::zoomNotch (bool inward)
+{
+	zoom() *= inward ? 0.833f : 1.2f;
+}
+
+// =============================================================================
+//
+void GLRenderer::zoomToFit()
+{
+	zoom() = 30.0f;
+
+	if (document() == null or m_width == -1 or m_height == -1)
+		return;
+
+	bool lastfilled = false;
+	bool firstrun = true;
+	enum { black = 0xFF000000 };
+	bool inward = true;
+	int runaway = 50;
+
+	// Use the pick list while drawing the scene, this way we can tell whether borders
+	// are background or not.
+	setPicking (true);
+
+	while (--runaway)
+	{
+		if (zoom() > 10000.0 or zoom() < 0.0)
+		{
+			// Nothing to draw if we get here.
+			zoom() = 30.0;
+			break;
+		}
+
+		zoomNotch (inward);
+		QVector<unsigned char> capture (4 * m_width * m_height);
+		drawGLScene();
+		glReadPixels (0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, capture.data());
+		QImage image (capture.constData(), m_width, m_height, QImage::Format_ARGB32);
+		bool filled = false;
+
+		// Check the top and bottom rows
+		for (int i = 0; i < image.width(); ++i)
+		{
+			if (image.pixel (i, 0) != black or image.pixel (i, m_height - 1) != black)
+			{
+				filled = true;
+				break;
+			}
+		}
+
+		// Left and right edges
+		if (filled == false)
+		{
+			for (int i = 0; i < image.height(); ++i)
+			{
+				if (image.pixel (0, i) != black or image.pixel (m_width - 1, i) != black)
+				{
+					filled = true;
+					break;
+				}
+			}
+		}
+
+		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 = not 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 and not 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 (not filled and lastfilled)
+				break;
+
+			inward = not filled;
+		}
+
+		lastfilled = filled;
+	}
+
+	setPicking (false);
+}
+
+// =============================================================================
+//
+void GLRenderer::zoomAllToFit()
+{
+	zoomToFit();
+}
+
+// =============================================================================
+//
+void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev)
+{
+	if (m_editmode->mouseDoubleClicked (ev))
+		ev->accept();
+}
+
+// =============================================================================
+//
+LDOverlayPtr GLRenderer::findOverlayObject (ECamera cam)
+{
+	for (LDObjectPtr obj : document()->objects())
+	{
+		LDOverlayPtr ovlobj = obj.dynamicCast<LDOverlay>();
+
+		if (ovlobj != null and obj.staticCast<LDOverlay>()->camera() == cam)
+			return ovlobj;
+	}
+
+	return LDOverlayPtr();
+}
+
+// =============================================================================
+//
+// Read in overlays from the current file and update overlay info accordingly.
+//
+void GLRenderer::initOverlaysFromObjects()
+{
+	for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam)
+	{
+		if (cam == EFreeCamera)
+			continue;
+
+		LDGLOverlay& meta = currentDocumentData().overlays[cam];
+		LDOverlayPtr ovlobj = findOverlayObject (cam);
+
+		if (ovlobj == null and meta.img != null)
+		{
+			delete meta.img;
+			meta.img = null;
+		}
+		elif (ovlobj != null and
+			(meta.img == null or meta.fname != ovlobj->fileName()) and
+			not meta.invalid)
+		{
+			setupOverlay (cam, ovlobj->fileName(), ovlobj->x(),
+				ovlobj->y(), ovlobj->width(), ovlobj->height());
+		}
+	}
+}
+
+// =============================================================================
+//
+void GLRenderer::updateOverlayObjects()
+{
+	for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam)
+	{
+		if (cam == EFreeCamera)
+			continue;
+
+		LDGLOverlay& meta = currentDocumentData().overlays[cam];
+		LDOverlayPtr ovlobj = findOverlayObject (cam);
+
+		if (meta.img == null and ovlobj != null)
+		{
+			// If this is the last overlay image, we need to remove the empty space after it as well.
+			LDObjectPtr nextobj = ovlobj->next();
+
+			if (nextobj and nextobj->type() == OBJ_Empty)
+				nextobj->destroy();
+
+			// If the overlay object was there and the overlay itself is
+			// not, remove the object.
+			ovlobj->destroy();
+		}
+		elif (meta.img != null and ovlobj == null)
+		{
+			// Inverse case: image is there but the overlay object is
+			// not, thus create the object.
+			ovlobj = LDSpawn<LDOverlay>();
+
+			// Find a suitable position to place this object. We want to place
+			// this into the header, which is everything up to the first scemantic
+			// object. If we find another overlay object, place this object after
+			// the last one found. Otherwise, place it before the first schemantic
+			// object and put an empty object after it (though don't do this if
+			// there was no schemantic elements at all)
+			int i, lastOverlay = -1;
+			bool found = false;
+
+			for (i = 0; i < document()->getObjectCount(); ++i)
+			{
+				LDObjectPtr obj = document()->getObject (i);
+
+				if (obj->isScemantic())
+				{
+					found = true;
+					break;
+				}
+
+				if (obj->type() == OBJ_Overlay)
+					lastOverlay = i;
+			}
+
+			if (lastOverlay != -1)
+				document()->insertObj (lastOverlay + 1, ovlobj);
+			else
+			{
+				document()->insertObj (i, ovlobj);
+
+				if (found)
+					document()->insertObj (i + 1, LDSpawn<LDEmpty>());
+			}
+		}
+
+		if (meta.img != null and ovlobj != null)
+		{
+			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();
+}
+
+// =============================================================================
+//
+void GLRenderer::highlightCursorObject()
+{
+	if (not cfg::HighlightObjectBelowCursor and objectAtCursor() == null)
+		return;
+
+	LDObjectWeakPtr newObject;
+	LDObjectWeakPtr oldObject = objectAtCursor();
+	qint32 newIndex;
+
+	if (isCameraMoving() or not cfg::HighlightObjectBelowCursor)
+	{
+		newIndex = 0;
+	}
+	else
+	{
+		setPicking (true);
+		drawGLScene();
+		setPicking (false);
+
+		unsigned char pixel[4];
+		glReadPixels (m_mousePosition.x(), m_height - m_mousePosition.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel[0]);
+		newIndex = pixel[0] * 0x10000 | pixel[1] * 0x100 | pixel[2];
+	}
+
+	if (newIndex != (oldObject != null ? oldObject.toStrongRef()->id() : 0))
+	{
+		if (newIndex != 0)
+			newObject = LDObject::fromID (newIndex);
+
+		setObjectAtCursor (newObject);
+
+		if (oldObject != null)
+			compileObject (oldObject);
+
+		if (newObject != null)
+			compileObject (newObject);
+	}
+
+	update();
+}
+
+void GLRenderer::dragEnterEvent (QDragEnterEvent* ev)
+{
+	if (g_win != null and ev->source() == g_win->getPrimitivesTree() and g_win->getPrimitivesTree()->currentItem() != null)
+		ev->acceptProposedAction();
+}
+
+void GLRenderer::dropEvent (QDropEvent* ev)
+{
+	if (g_win != null and ev->source() == g_win->getPrimitivesTree())
+	{
+		QString primName = static_cast<SubfileListItem*> (g_win->getPrimitivesTree()->currentItem())->primitive()->name;
+		LDSubfilePtr ref = LDSpawn<LDSubfile>();
+		ref->setColor (MainColor());
+		ref->setFileInfo (GetDocument (primName));
+		ref->setPosition (Origin);
+		ref->setTransform (IdentityMatrix);
+		LDDocument::current()->insertObj (g_win->getInsertionPoint(), ref);
+		ref->select();
+		g_win->buildObjList();
+		g_win->R()->refresh();
+		ev->acceptProposedAction();
+	}
+}
+
+Vertex const& GLRenderer::position3D() const
+{
+	return m_position3D;
+}
+
+LDFixedCamera const& GLRenderer::getFixedCamera (ECamera cam) const
+{
+	return g_FixedCameras[cam];
+}
+
+bool GLRenderer::mouseHasMoved() const
+{
+	return m_totalmove >= 10;
+}
+
+QPoint const& GLRenderer::mousePosition() const
+{
+	return m_mousePosition;
+}
+
+QPointF const& GLRenderer::mousePositionF() const
+{
+	return m_mousePositionF;
+}
+
+void GLRenderer::doMakeCurrent()
+{
+	makeCurrent();
+	initializeOpenGLFunctions();
+}
+
+int GLRenderer::depthNegateFactor() const
+{
+	return g_FixedCameras[camera()].negatedDepth ? -1 : 1;
+}
+
+Qt::KeyboardModifiers GLRenderer::keyboardModifiers() const
+{
+	return m_keymods;
+}
+
+LDFixedCamera const& GetFixedCamera (ECamera cam)
+{
+	assert (cam != EFreeCamera);
+	return g_FixedCameras[cam];
+}
--- a/src/ldConfig.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,216 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QFile>
-#include "ldDocument.h"
-#include "ldConfig.h"
-#include "mainWindow.h"
-#include "miscallenous.h"
-#include "colors.h"
-
-// =============================================================================
-//
-// Helper function for parseLDConfig
-//
-static bool ParseLDConfigTag (LDConfigParser& pars, char const* tag, QString& val)
-{
-	int pos;
-
-	// Try find the token and get its position
-	if (not pars.findToken (pos, tag, 1))
-		return false;
-
-	// Get the token after it and store it into val
-	return pars.getToken (val, pos + 1);
-}
-
-// =============================================================================
-//
-void LDConfigParser::parseLDConfig()
-{
-	QFile* fp = OpenLDrawFile ("LDConfig.ldr", false);
-
-	if (fp == null)
-	{
-		Critical (QObject::tr ("Unable to open LDConfig.ldr for parsing."));
-		return;
-	}
-
-	// Read in the lines
-	while (not fp->atEnd())
-	{
-		QString line = QString::fromUtf8 (fp->readLine());
-
-		if (line.isEmpty() or line[0] != '0')
-			continue; // empty or illogical
-
-		line.remove ('\r');
-		line.remove ('\n');
-
-		// Parse the line
-		LDConfigParser pars (line, ' ');
-
-		int code = 0, alpha = 255;
-		QString name, facename, edgename, valuestr;
-
-		// Check 0 !COLOUR, parse the name
-		if (not pars.tokenCompare (0, "0") or
-			not pars.tokenCompare (1, "!COLOUR") or
-			not pars.getToken (name, 2))
-		{
-			continue;
-		}
-
-		// Replace underscores in the name with spaces for readability
-		name.replace ("_", " ");
-
-		// Get the CODE tag
-		if (not ParseLDConfigTag (pars, "CODE", valuestr))
-			continue;
-
-		// Ensure that the code is within [0 - 511]
-		bool ok;
-		code = valuestr.toShort (&ok);
-
-		if (not ok or code < 0 or code >= 512)
-			continue;
-
-		// VALUE and EDGE tags
-		if (not ParseLDConfigTag (pars, "VALUE", facename) or not ParseLDConfigTag (pars, "EDGE", edgename))
-			continue;
-
-		// Ensure that our colors are correct
-		QColor faceColor (facename),
-			edgeColor (edgename);
-
-		if (not faceColor.isValid() or not edgeColor.isValid())
-			continue;
-
-		// Parse alpha if given.
-		if (ParseLDConfigTag (pars, "ALPHA", valuestr))
-			alpha = Clamp (valuestr.toInt(), 0, 255);
-
-		LDColorData* col = new LDColorData;
-		col->m_name = name;
-		col->m_faceColor = faceColor;
-		col->m_edgeColor = edgeColor;
-		col->m_hexcode = facename;
-		col->m_faceColor.setAlpha (alpha);
-		col->m_index = code;
-		LDColor::addLDConfigColor (code, LDColor (col));
-	}
-
-	fp->close();
-	fp->deleteLater();
-}
-
-// =============================================================================
-//
-LDConfigParser::LDConfigParser (QString inText, char sep)
-{
-	m_tokens = inText.split (sep, QString::SkipEmptyParts);
-	m_pos = -1;
-}
-
-// =============================================================================
-//
-bool LDConfigParser::isAtBeginning()
-{
-	return m_pos == -1;
-}
-
-// =============================================================================
-//
-bool LDConfigParser::isAtEnd()
-{
-	return m_pos == m_tokens.size() - 1;
-}
-
-// =============================================================================
-//
-bool LDConfigParser::getToken (QString& val, const int pos)
-{
-	if (pos >= m_tokens.size())
-		return false;
-
-	val = m_tokens[pos];
-	return true;
-}
-
-// =============================================================================
-//
-bool LDConfigParser::getNextToken (QString& val)
-{
-	return getToken (val, ++m_pos);
-}
-
-// =============================================================================
-//
-bool LDConfigParser::peekNextToken (QString& val)
-{
-	return getToken (val, m_pos + 1);
-}
-
-// =============================================================================
-//
-bool LDConfigParser::findToken (int& result, char const* needle, int args)
-{
-	for (int i = 0; i < (m_tokens.size() - args); ++i)
-	{
-		if (m_tokens[i] == needle)
-		{
-			result = i;
-			return true;
-		}
-	}
-
-	return false;
-}
-
-// =============================================================================
-//
-void LDConfigParser::rewind()
-{
-	m_pos = -1;
-}
-
-// =============================================================================
-//
-void LDConfigParser::seek (int amount, bool rel)
-{
-	m_pos = (rel ? m_pos : 0) + amount;
-}
-
-// =============================================================================
-//
-int LDConfigParser::getSize()
-{
-	return m_tokens.size();
-}
-
-// =============================================================================
-//
-bool LDConfigParser::tokenCompare (int inPos, const char* sOther)
-{
-	QString tok;
-
-	if (not getToken (tok, inPos))
-		return false;
-
-	return (tok == sOther);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldConfig.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,216 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QFile>
+#include "ldDocument.h"
+#include "ldConfig.h"
+#include "mainWindow.h"
+#include "miscallenous.h"
+#include "colors.h"
+
+// =============================================================================
+//
+// Helper function for parseLDConfig
+//
+static bool ParseLDConfigTag (LDConfigParser& pars, char const* tag, QString& val)
+{
+	int pos;
+
+	// Try find the token and get its position
+	if (not pars.findToken (pos, tag, 1))
+		return false;
+
+	// Get the token after it and store it into val
+	return pars.getToken (val, pos + 1);
+}
+
+// =============================================================================
+//
+void LDConfigParser::parseLDConfig()
+{
+	QFile* fp = OpenLDrawFile ("LDConfig.ldr", false);
+
+	if (fp == null)
+	{
+		Critical (QObject::tr ("Unable to open LDConfig.ldr for parsing."));
+		return;
+	}
+
+	// Read in the lines
+	while (not fp->atEnd())
+	{
+		QString line = QString::fromUtf8 (fp->readLine());
+
+		if (line.isEmpty() or line[0] != '0')
+			continue; // empty or illogical
+
+		line.remove ('\r');
+		line.remove ('\n');
+
+		// Parse the line
+		LDConfigParser pars (line, ' ');
+
+		int code = 0, alpha = 255;
+		QString name, facename, edgename, valuestr;
+
+		// Check 0 !COLOUR, parse the name
+		if (not pars.tokenCompare (0, "0") or
+			not pars.tokenCompare (1, "!COLOUR") or
+			not pars.getToken (name, 2))
+		{
+			continue;
+		}
+
+		// Replace underscores in the name with spaces for readability
+		name.replace ("_", " ");
+
+		// Get the CODE tag
+		if (not ParseLDConfigTag (pars, "CODE", valuestr))
+			continue;
+
+		// Ensure that the code is within [0 - 511]
+		bool ok;
+		code = valuestr.toShort (&ok);
+
+		if (not ok or code < 0 or code >= 512)
+			continue;
+
+		// VALUE and EDGE tags
+		if (not ParseLDConfigTag (pars, "VALUE", facename) or not ParseLDConfigTag (pars, "EDGE", edgename))
+			continue;
+
+		// Ensure that our colors are correct
+		QColor faceColor (facename),
+			edgeColor (edgename);
+
+		if (not faceColor.isValid() or not edgeColor.isValid())
+			continue;
+
+		// Parse alpha if given.
+		if (ParseLDConfigTag (pars, "ALPHA", valuestr))
+			alpha = Clamp (valuestr.toInt(), 0, 255);
+
+		LDColorData* col = new LDColorData;
+		col->m_name = name;
+		col->m_faceColor = faceColor;
+		col->m_edgeColor = edgeColor;
+		col->m_hexcode = facename;
+		col->m_faceColor.setAlpha (alpha);
+		col->m_index = code;
+		LDColor::addLDConfigColor (code, LDColor (col));
+	}
+
+	fp->close();
+	fp->deleteLater();
+}
+
+// =============================================================================
+//
+LDConfigParser::LDConfigParser (QString inText, char sep)
+{
+	m_tokens = inText.split (sep, QString::SkipEmptyParts);
+	m_pos = -1;
+}
+
+// =============================================================================
+//
+bool LDConfigParser::isAtBeginning()
+{
+	return m_pos == -1;
+}
+
+// =============================================================================
+//
+bool LDConfigParser::isAtEnd()
+{
+	return m_pos == m_tokens.size() - 1;
+}
+
+// =============================================================================
+//
+bool LDConfigParser::getToken (QString& val, const int pos)
+{
+	if (pos >= m_tokens.size())
+		return false;
+
+	val = m_tokens[pos];
+	return true;
+}
+
+// =============================================================================
+//
+bool LDConfigParser::getNextToken (QString& val)
+{
+	return getToken (val, ++m_pos);
+}
+
+// =============================================================================
+//
+bool LDConfigParser::peekNextToken (QString& val)
+{
+	return getToken (val, m_pos + 1);
+}
+
+// =============================================================================
+//
+bool LDConfigParser::findToken (int& result, char const* needle, int args)
+{
+	for (int i = 0; i < (m_tokens.size() - args); ++i)
+	{
+		if (m_tokens[i] == needle)
+		{
+			result = i;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+// =============================================================================
+//
+void LDConfigParser::rewind()
+{
+	m_pos = -1;
+}
+
+// =============================================================================
+//
+void LDConfigParser::seek (int amount, bool rel)
+{
+	m_pos = (rel ? m_pos : 0) + amount;
+}
+
+// =============================================================================
+//
+int LDConfigParser::getSize()
+{
+	return m_tokens.size();
+}
+
+// =============================================================================
+//
+bool LDConfigParser::tokenCompare (int inPos, const char* sOther)
+{
+	QString tok;
+
+	if (not getToken (tok, inPos))
+		return false;
+
+	return (tok == sOther);
+}
--- a/src/ldDocument.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1540 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QMessageBox>
-#include <QFileDialog>
-#include <QDir>
-#include <QTime>
-#include <QApplication>
-
-#include "main.h"
-#include "configuration.h"
-#include "ldDocument.h"
-#include "miscallenous.h"
-#include "mainWindow.h"
-#include "editHistory.h"
-#include "dialogs.h"
-#include "glRenderer.h"
-#include "glCompiler.h"
-#include "partDownloader.h"
-
-CFGENTRY (String, LDrawPath, "")
-CFGENTRY (List, RecentFiles, {})
-CFGENTRY (Bool, TryDownloadMissingFiles, false)
-EXTERN_CFGENTRY (String, DownloadFilePath)
-EXTERN_CFGENTRY (Bool, UseLogoStuds)
-
-static bool g_loadingMainFile = false;
-static const int g_maxRecentFiles = 10;
-static bool g_aborted = false;
-static LDDocumentPtr g_logoedStud;
-static LDDocumentPtr g_logoedStud2;
-static QList<LDDocumentWeakPtr> g_allDocuments;
-static QList<LDDocumentPtr> g_explicitDocuments;
-static LDDocumentPtr g_currentDocument;
-static bool g_loadingLogoedStuds = false;
-
-const QStringList g_specialSubdirectories ({ "s", "48", "8" });
-
-// =============================================================================
-//
-namespace LDPaths
-{
-	static QString pathError;
-
-	struct
-	{
-		QString LDConfigPath;
-		QString partsPath, primsPath;
-	} pathInfo;
-
-	void initPaths()
-	{
-		if (not tryConfigure (cfg::LDrawPath))
-		{
-			LDrawPathDialog dlg (false);
-
-			if (not dlg.exec())
-				Exit();
-
-			cfg::LDrawPath = dlg.filename();
-		}
-	}
-
-	bool tryConfigure (QString path)
-	{
-		QDir dir;
-
-		if (not dir.cd (path))
-		{
-			pathError = "Directory does not exist.";
-			return false;
-		}
-
-		QStringList mustHave = { "LDConfig.ldr", "parts", "p" };
-		QStringList contents = dir.entryList (mustHave);
-
-		if (contents.size() != mustHave.size())
-		{
-			pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/.";
-			return false;
-		}
-
-		pathInfo.partsPath = format ("%1" DIRSLASH "parts", path);
-		pathInfo.LDConfigPath = format ("%1" DIRSLASH "LDConfig.ldr", path);
-		pathInfo.primsPath = format ("%1" DIRSLASH "p", path);
-
-		return true;
-	}
-
-	// Accessors
-	QString getError()
-	{
-		return pathError;
-	}
-
-	QString ldconfig()
-	{
-		return pathInfo.LDConfigPath;
-	}
-
-	QString prims()
-	{
-		return pathInfo.primsPath;
-	}
-
-	QString parts()
-	{
-		return pathInfo.partsPath;
-	}
-}
-
-// =============================================================================
-//
-LDDocument::LDDocument (LDDocumentPtr* selfptr) :
-	m_isImplicit (true),
-	m_flags (0),
-	m_verticesOutdated (true),
-	m_needVertexMerge (true),
-	m_gldata (new LDGLData)
-{
-	*selfptr = LDDocumentPtr (this);
-	setSelf (*selfptr);
-	setSavePosition (-1);
-	setTabIndex (-1);
-	setHistory (new History);
-	history()->setDocument (*selfptr);
-	m_needsReCache = true;
-	g_allDocuments << *selfptr;
-}
-
-// =============================================================================
-//
-LDDocumentPtr LDDocument::createNew()
-{
-	LDDocumentPtr ptr;
-	new LDDocument (&ptr);
-	return ptr;
-}
-
-// =============================================================================
-//
-LDDocument::~LDDocument()
-{
-	// Don't bother during program termination
-	if (IsExiting())
-		return;
-
-	g_allDocuments.removeOne (self());
-	m_flags |= DOCF_IsBeingDestroyed;
-	delete m_history;
-	delete m_gldata;
-}
-
-// =============================================================================
-//
-void LDDocument::setImplicit (bool const& a)
-{
-	if (m_isImplicit != a)
-	{
-		m_isImplicit = a;
-
-		if (a == false)
-		{
-			g_explicitDocuments << self().toStrongRef();
-			print ("Opened %1", name());
-
-			// Implicit files are not compiled by the GL renderer. Now that this
-			// part is no longer implicit, it needs to be compiled.
-			if (g_win != null)
-				g_win->R()->compiler()->compileDocument (self());
-		}
-		else
-		{
-			g_explicitDocuments.removeOne (self().toStrongRef());
-			print ("Closed %1", name());
-		}
-
-		if (g_win != null)
-			g_win->updateDocumentList();
-
-		// If the current document just became implicit (e.g. it was 'closed'),
-		// we need to get a new current document.
-		if (current() == self() and isImplicit())
-		{
-			if (explicitDocuments().isEmpty())
-				newFile();
-			else
-				setCurrent (explicitDocuments().first());
-		}
-	}
-}
-
-// =============================================================================
-//
-QList<LDDocumentPtr> const& LDDocument::explicitDocuments()
-{
-	return g_explicitDocuments;
-}
-
-// =============================================================================
-//
-LDDocumentPtr FindDocument (QString name)
-{
-	for (LDDocumentWeakPtr weakfile : g_allDocuments)
-	{
-		if (weakfile == null)
-			continue;
-
-		LDDocumentPtr file (weakfile.toStrongRef());
-
-		if (Eq (name, file->name(), file->defaultName()))
-			return file;
-	}
-
-	return LDDocumentPtr();
-}
-
-// =============================================================================
-//
-QString Dirname (QString path)
-{
-	long lastpos = path.lastIndexOf (DIRSLASH);
-
-	if (lastpos > 0)
-		return path.left (lastpos);
-
-#ifndef _WIN32
-	if (path[0] == DIRSLASH_CHAR)
-		return DIRSLASH;
-#endif // _WIN32
-
-	return "";
-}
-
-// =============================================================================
-//
-QString Basename (QString path)
-{
-	long lastpos = path.lastIndexOf (DIRSLASH);
-
-	if (lastpos != -1)
-		return path.mid (lastpos + 1);
-
-	return path;
-}
-
-// =============================================================================
-//
-static QString FindDocumentPath (QString relpath, bool subdirs)
-{
-	QString fullPath;
-
-	// LDraw models use Windows-style path separators. If we're not on Windows,
-	// replace the path separator now before opening any files. Qt expects
-	// forward-slashes as directory separators.
-#ifndef WIN32
-	relpath.replace ("\\", "/");
-#endif // WIN32
-
-	// Try find it relative to other currently open documents. We want a file
-	// in the immediate vicinity of a current model to override stock LDraw stuff.
-	QString reltop = Basename (Dirname (relpath));
-
-	for (LDDocumentWeakPtr doc : g_allDocuments)
-	{
-		if (doc == null)
-			continue;
-
-		QString partpath = format ("%1/%2", Dirname (doc.toStrongRef()->fullPath()), relpath);
-		QFile f (partpath);
-
-		if (f.exists())
-		{
-			// ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48
-			QString proptop = Basename (Dirname (partpath));
-
-			bool bogus = false;
-
-			for (QString s : g_specialSubdirectories)
-			{
-				if ((proptop == s and reltop != s) or (reltop == s and proptop != s))
-				{
-					bogus = true;
-					break;
-				}
-			}
-
-			if (not bogus)
-				return partpath;
-		}
-	}
-
-	if (QFile::exists (relpath))
-		return relpath;
-
-	// Try with just the LDraw path first
-	fullPath = format ("%1" DIRSLASH "%2", cfg::LDrawPath, relpath);
-
-	if (QFile::exists (fullPath))
-		return fullPath;
-
-	if (subdirs)
-	{
-		// Look in sub-directories: parts and p. Also look in net_downloadpath, since that's
-		// where we download parts from the PT to.
-		for (const QString& topdir : QList<QString> ({ cfg::LDrawPath, cfg::DownloadFilePath }))
-		{
-			for (const QString& subdir : QList<QString> ({ "parts", "p" }))
-			{
-				fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath);
-
-				if (QFile::exists (fullPath))
-					return fullPath;
-			}
-		}
-	}
-
-	// Did not find the file.
-	return "";
-}
-
-// =============================================================================
-//
-QFile* OpenLDrawFile (QString relpath, bool subdirs, QString* pathpointer)
-{
-	print ("Opening %1...\n", relpath);
-	QString path = FindDocumentPath (relpath, subdirs);
-
-	if (pathpointer != null)
-		*pathpointer = path;
-
-	if (path.isEmpty())
-		return null;
-
-	QFile* fp = new QFile (path);
-
-	if (fp->open (QIODevice::ReadOnly))
-		return fp;
-
-	fp->deleteLater();
-	return null;
-}
-
-// =============================================================================
-//
-void LDFileLoader::start()
-{
-	setDone (false);
-	setProgress (0);
-	setAborted (false);
-
-	if (isOnForeground())
-	{
-		g_aborted = false;
-
-		// Show a progress dialog if we're loading the main ldDocument.here so we can
-		// show progress updates and keep the WM posted that we're still here.
-		// Of course we cannot exec() the dialog because then the dialog would
-		// block.
-		dlg = new OpenProgressDialog (g_win);
-		dlg->setNumLines (lines().size());
-		dlg->setModal (true);
-		dlg->show();
-
-		// Connect the loader in so we can show updates
-		connect (this, SIGNAL (workDone()), dlg, SLOT (accept()));
-		connect (dlg, SIGNAL (rejected()), this, SLOT (abort()));
-	}
-	else
-		dlg = null;
-
-	// Begin working
-	work (0);
-}
-
-// =============================================================================
-//
-void LDFileLoader::work (int i)
-{
-	// User wishes to abort, so stop here now.
-	if (isAborted())
-	{
-		for (LDObjectPtr obj : m_objects)
-			obj->destroy();
-
-		m_objects.clear();
-		setDone (true);
-		return;
-	}
-
-	// Parse up to 300 lines per iteration
-	int max = i + 300;
-
-	for (; i < max and i < (int) lines().size(); ++i)
-	{
-		QString line = lines()[i];
-
-		// Trim the trailing newline
-		QChar c;
-
-		while (line.endsWith ("\n") or line.endsWith ("\r"))
-			line.chop (1);
-
-		LDObjectPtr obj = ParseLine (line);
-
-		// Check for parse errors and warn about tthem
-		if (obj->type() == OBJ_Error)
-		{
-			print ("Couldn't parse line #%1: %2",
-				progress() + 1, obj.staticCast<LDError>()->reason());
-
-			if (warnings() != null)
-				(*warnings())++;
-		}
-
-		m_objects << obj;
-		setProgress (i);
-
-		// If we have a dialog pointer, update the progress now
-		if (isOnForeground())
-			dlg->updateProgress (i);
-	}
-
-	// If we're done now, tell the environment we're done and stop.
-	if (i >= ((int) lines().size()) - 1)
-	{
-		emit workDone();
-		setDone (true);
-		return;
-	}
-
-	// Otherwise, continue, by recursing back.
-	if (not isDone())
-	{
-		// If we have a dialog to show progress output to, we cannot just call
-		// work() again immediately as the dialog needs some processor cycles as
-		// well. Thus, take a detour through the event loop by using the
-		// meta-object system.
-		//
-		// This terminates the loop here and control goes back to the function
-		// which called the file loader. It will keep processing the event loop
-		// until we're ready (see loadFileContents), thus the event loop will
-		// eventually catch the invokation we throw here and send us back. Though
-		// it's not technically recursion anymore, more like a for loop. :P
-		if (isOnForeground())
-			QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i));
-		else
-			work (i);
-	}
-}
-
-// =============================================================================
-//
-void LDFileLoader::abort()
-{
-	setAborted (true);
-
-	if (isOnForeground())
-		g_aborted = true;
-}
-
-// =============================================================================
-//
-LDObjectList LoadFileContents (QFile* fp, int* numWarnings, bool* ok)
-{
-	QStringList lines;
-	LDObjectList objs;
-
-	if (numWarnings)
-		*numWarnings = 0;
-
-	// Read in the lines
-	while (not fp->atEnd())
-		lines << QString::fromUtf8 (fp->readLine());
-
-	LDFileLoader* loader = new LDFileLoader;
-	loader->setWarnings (numWarnings);
-	loader->setLines (lines);
-	loader->setOnForeground (g_loadingMainFile);
-	loader->start();
-
-	// After start() returns, if the loader isn't done yet, it's delaying
-	// its next iteration through the event loop. We need to catch this here
-	// by telling the event loop to tick, which will tick the file loader again.
-	// We keep doing this until the file loader is ready.
-	while (not loader->isDone())
-		qApp->processEvents();
-
-	// If we wanted the success value, supply that now
-	if (ok)
-		*ok = not loader->isAborted();
-
-	objs = loader->objects();
-	delete loader;
-	return objs;
-}
-
-// =============================================================================
-//
-LDDocumentPtr OpenDocument (QString path, bool search, bool implicit, LDDocumentPtr fileToOverride)
-{
-	// Convert the file name to lowercase when searching because some parts contain subfile
-	// subfile references with uppercase file names. I'll assume here that the library will always
-	// use lowercase file names for the part files.
-	QFile* fp;
-	QString fullpath;
-
-	if (search)
-	{
-		fp = OpenLDrawFile (path.toLower(), true, &fullpath);
-	}
-	else
-	{
-		fp = new QFile (path);
-		fullpath = path;
-
-		if (not fp->open (QIODevice::ReadOnly))
-		{
-			delete fp;
-			return LDDocumentPtr();
-		}
-	}
-
-	if (not fp)
-		return LDDocumentPtr();
-
-	LDDocumentPtr load = (fileToOverride != null ? fileToOverride : LDDocument::createNew());
-	load->setImplicit (implicit);
-	load->setFullPath (fullpath);
-	load->setName (LDDocument::shortenName (load->fullPath()));
-
-	// Loading the file shouldn't count as actual edits to the document.
-	load->history()->setIgnoring (true);
-
-	int numWarnings;
-	bool ok;
-	LDObjectList objs = LoadFileContents (fp, &numWarnings, &ok);
-	fp->close();
-	fp->deleteLater();
-
-	if (not ok)
-	{
-		load->dismiss();
-		return LDDocumentPtr();
-	}
-
-	load->addObjects (objs);
-
-	if (g_loadingMainFile)
-	{
-		LDDocument::setCurrent (load);
-		g_win->R()->setDocument (load);
-		print (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings);
-	}
-
-	load->history()->setIgnoring (false);
-	return load;
-}
-
-// =============================================================================
-//
-bool LDDocument::isSafeToClose()
-{
-	using msgbox = QMessageBox;
-	setlocale (LC_ALL, "C");
-
-	// If we have unsaved changes, warn and give the option of saving.
-	if (hasUnsavedChanges())
-	{
-		QString message = format (QObject::tr ("There are unsaved changes to %1. Should it be saved?"), getDisplayName());
-
-		int button = msgbox::question (g_win, QObject::tr ("Unsaved Changes"), message,
-			(msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel);
-
-		switch (button)
-		{
-			case msgbox::Yes:
-			{
-				// If we don't have a file path yet, we have to ask the user for one.
-				if (name().length() == 0)
-				{
-					QString newpath = QFileDialog::getSaveFileName (g_win, QObject::tr ("Save As"),
-						CurrentDocument()->name(), QObject::tr ("LDraw files (*.dat *.ldr)"));
-
-					if (newpath.length() == 0)
-						return false;
-
-					setName (newpath);
-				}
-
-				if (not save())
-				{
-					message = format (QObject::tr ("Failed to save %1 (%2)\nDo you still want to close?"),
-						name(), strerror (errno));
-
-					if (msgbox::critical (g_win, QObject::tr ("Save Failure"), message,
-						(msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No)
-					{
-						return false;
-					}
-				}
-				break;
-			}
-
-			case msgbox::Cancel:
-				return false;
-
-			default:
-				break;
-		}
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-void CloseAllDocuments()
-{
-	for (LDDocumentPtr file : g_explicitDocuments)
-		file->dismiss();
-}
-
-// =============================================================================
-//
-void newFile()
-{
-	// Create a new anonymous file and set it to our current
-	LDDocumentPtr f = LDDocument::createNew();
-	f->setName ("");
-	f->setImplicit (false);
-	LDDocument::setCurrent (f);
-	LDDocument::closeInitialFile();
-	g_win->R()->setDocument (f);
-	g_win->doFullRefresh();
-	g_win->updateTitle();
-	g_win->updateActions();
-}
-
-// =============================================================================
-//
-void AddRecentFile (QString path)
-{
-	int idx = cfg::RecentFiles.indexOf (path);
-
-	// If this file already is in the list, pop it out.
-	if (idx != -1)
-	{
-		if (idx == cfg::RecentFiles.size() - 1)
-			return; // first recent file - abort and do nothing
-
-		cfg::RecentFiles.removeAt (idx);
-	}
-
-	// If there's too many recent files, drop one out.
-	while (cfg::RecentFiles.size() > (g_maxRecentFiles - 1))
-		cfg::RecentFiles.removeAt (0);
-
-	// Add the file
-	cfg::RecentFiles << path;
-
-	Config::Save();
-	g_win->updateRecentFilesMenu();
-}
-
-// =============================================================================
-// Open an LDraw file and set it as the main model
-// =============================================================================
-void OpenMainModel (QString path)
-{
-	// If there's already a file with the same name, this file must replace it.
-	LDDocumentPtr documentToReplace;
-	LDDocumentPtr file;
-	QString shortName = LDDocument::shortenName (path);
-
-	for (LDDocumentWeakPtr doc : g_allDocuments)
-	{
-		if (doc != null and doc.toStrongRef()->name() == shortName)
-		{
-			documentToReplace = doc;
-			break;
-		}
-	}
-
-	// We cannot open this file if the document this would replace is not
-	// safe to close.
-	if (documentToReplace != null and not documentToReplace->isSafeToClose())
-		return;
-
-	g_loadingMainFile = true;
-
-	// If we're replacing an existing document, clear the document and
-	// make it ready for being loaded to.
-	if (documentToReplace != null)
-	{
-		file = documentToReplace;
-		file->clear();
-	}
-
-	file = OpenDocument (path, false, false, file);
-
-	if (file == null)
-	{
-		if (not g_aborted)
-		{
-			// Tell the user loading failed.
-			setlocale (LC_ALL, "C");
-			Critical (format (QObject::tr ("Failed to open %1: %2"), path, strerror (errno)));
-		}
-
-		g_loadingMainFile = false;
-		return;
-	}
-
-	file->setImplicit (false);
-
-	// 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;
-
-	// If there were problems loading subfile references, try see if we can find these
-	// files on the parts tracker.
-	QStringList unknowns;
-
-	for (LDObjectPtr obj : file->objects())
-	{
-		if (obj->type() != OBJ_Error or obj.staticCast<LDError>()->fileReferenced().isEmpty())
-			continue;
-
-		unknowns << obj.staticCast<LDError>()->fileReferenced();
-	}
-
-	if (cfg::TryDownloadMissingFiles and not unknowns.isEmpty())
-	{
-		PartDownloader dl;
-
-		if (dl.checkValidPath())
-		{
-			dl.setSource (PartDownloader::PartsTracker);
-			dl.setPrimaryFile (file);
-
-			for (QString const& unknown : unknowns)
-				dl.downloadFromPartsTracker (unknown);
-
-			dl.exec();
-			dl.checkIfFinished();
-			file->reloadAllSubfiles();
-		}
-	}
-}
-
-// =============================================================================
-//
-bool LDDocument::save (QString path, int64* sizeptr)
-{
-	if (isImplicit())
-		return false;
-
-	if (not path.length())
-		path = fullPath();
-
-	// If the second object in the list holds the file name, update that now.
-	LDObjectPtr nameObject = getObject (1);
-
-	if (nameObject != null and nameObject->type() == OBJ_Comment)
-	{
-		LDCommentPtr nameComment = nameObject.staticCast<LDComment>();
-
-		if (nameComment->text().left (6) == "Name: ")
-		{
-			QString newname = shortenName (path);
-			nameComment->setText (format ("Name: %1", newname));
-			g_win->buildObjList();
-		}
-	}
-
-	QByteArray data;
-
-	if (sizeptr != null)
-		*sizeptr = 0;
-
-	// 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 (LDObjectPtr obj : objects())
-	{
-		QByteArray subdata ((obj->asText() + "\r\n").toUtf8());
-		data.append (subdata);
-
-		if (sizeptr != null)
-			*sizeptr += subdata.size();
-	}
-
-	QFile f (path);
-
-	if (not f.open (QIODevice::WriteOnly))
-		return false;
-
-	f.write (data);
-	f.close();
-
-	// We have successfully saved, update the save position now.
-	setSavePosition (history()->position());
-	setFullPath (path);
-	setName (shortenName (path));
-
-	g_win->updateDocumentListItem (self().toStrongRef());
-	g_win->updateTitle();
-	return true;
-}
-
-// =============================================================================
-//
-void LDDocument::clear()
-{
-	for (LDObjectPtr obj : objects())
-		forgetObject (obj);
-}
-
-// =============================================================================
-//
-static void CheckTokenCount (const QStringList& tokens, int num)
-{
-	if (tokens.size() != num)
-		throw QString (format ("Bad amount of tokens, expected %1, got %2", num, tokens.size()));
-}
-
-// =============================================================================
-//
-static void CheckTokenNumbers (const QStringList& tokens, int min, int max)
-{
-	bool ok;
-
-	QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+");
-
-	for (int i = min; i <= max; ++i)
-	{
-		// Check for floating point
-		tokens[i].toDouble (&ok);
-		if (ok)
-			return;
-
-		// Check hex
-		if (tokens[i].startsWith ("0x"))
-		{
-			tokens[i].mid (2).toInt (&ok, 16);
-
-			if (ok)
-				return;
-		}
-
-		// Check scientific notation, e.g. 7.99361e-15
-		if (scient.exactMatch (tokens[i]))
-			return;
-
-		throw QString (format ("Token #%1 was `%2`, expected a number (matched length: %3)",
-			(i + 1), tokens[i], scient.matchedLength()));
-	}
-}
-
-// =============================================================================
-//
-static Vertex ParseVertex (QStringList& s, const int n)
-{
-	Vertex v;
-	v.apply ([&] (Axis ax, double& a) { a = s[n + ax].toDouble(); });
-	return v;
-}
-
-static int32 StringToNumber (QString a, bool* ok = null)
-{
-	int base = 10;
-
-	if (a.startsWith ("0x"))
-	{
-		a.remove (0, 2);
-		base = 16;
-	}
-
-	return a.toLong (ok, base);
-}
-
-// =============================================================================
-// 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.
-// =============================================================================
-LDObjectPtr ParseLine (QString line)
-{
-	try
-	{
-		QStringList tokens = line.split (" ", QString::SkipEmptyParts);
-
-		if (tokens.size() <= 0)
-		{
-			// Line was empty, or only consisted of whitespace
-			return LDSpawn<LDEmpty>();
-		}
-
-		if (tokens[0].length() != 1 or not tokens[0][0].isDigit())
-			throw QString ("Illogical line code");
-
-		int num = tokens[0][0].digitValue();
-
-		switch (num)
-		{
-			case 0:
-			{
-				// Comment
-				QString commentText (line.mid (line.indexOf ("0") + 2));
-				QString commentTextSimplified (commentText.simplified());
-
-				// Handle BFC statements
-				if (tokens.size() > 2 and tokens[1] == "BFC")
-				{
-					for_enum (BFCStatement, i)
-					{
-						if (commentTextSimplified == format ("BFC %1",
-							LDBFC::StatementStrings[int (i)]))
-						{
-							return LDSpawn<LDBFC> (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.
-					if (commentTextSimplified == "BFC CERTIFY INVERTNEXT")
-						return LDSpawn<LDBFC> (BFCStatement::InvertNext);
-					elif (commentTextSimplified == "BFC CERTIFY CLIP")
-						return LDSpawn<LDBFC> (BFCStatement::Clip);
-					elif (commentTextSimplified == "BFC CERTIFY NOCLIP")
-						return LDSpawn<LDBFC> (BFCStatement::NoClip);
-				}
-
-				if (tokens.size() > 2 and tokens[1] == "!LDFORGE")
-				{
-					// Handle LDForge-specific types, they're embedded into comments too
-					if (tokens[2] == "OVERLAY")
-					{
-						CheckTokenCount (tokens, 9);
-						CheckTokenNumbers (tokens, 5, 8);
-
-						LDOverlayPtr obj = LDSpawn<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:
-				LDCommentPtr obj = LDSpawn<LDComment>();
-				obj->setText (commentText);
-				return obj;
-			}
-
-			case 1:
-			{
-				// Subfile
-				CheckTokenCount (tokens, 15);
-				CheckTokenNumbers (tokens, 1, 13);
-
-				// Try open the file. Disable g_loadingMainFile temporarily since we're
-				// not loading the main file now, but the subfile in question.
-				bool tmp = g_loadingMainFile;
-				g_loadingMainFile = false;
-				LDDocumentPtr load = GetDocument (tokens[14]);
-				g_loadingMainFile = tmp;
-
-				// If we cannot open the file, mark it an error. Note we cannot use LDParseError
-				// here because the error object needs the document reference.
-				if (not load)
-				{
-					LDErrorPtr obj = LDSpawn<LDError> (line, format ("Could not open %1", tokens[14]));
-					obj->setFileReferenced (tokens[14]);
-					return obj;
-				}
-
-				LDSubfilePtr obj = LDSpawn<LDSubfile>();
-				obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1])));
-				obj->setPosition (ParseVertex (tokens, 2));  // 2 - 4
-
-				Matrix transform;
-
-				for (int i = 0; i < 9; ++i)
-					transform[i] = tokens[i + 5].toDouble(); // 5 - 13
-
-				obj->setTransform (transform);
-				obj->setFileInfo (load);
-				return obj;
-			}
-
-			case 2:
-			{
-				CheckTokenCount (tokens, 8);
-				CheckTokenNumbers (tokens, 1, 7);
-
-				// Line
-				LDLinePtr obj (LDSpawn<LDLine>());
-				obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1])));
-
-				for (int i = 0; i < 2; ++i)
-					obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3)));   // 2 - 7
-
-				return obj;
-			}
-
-			case 3:
-			{
-				CheckTokenCount (tokens, 11);
-				CheckTokenNumbers (tokens, 1, 10);
-
-				// Triangle
-				LDTrianglePtr obj (LDSpawn<LDTriangle>());
-				obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1])));
-
-				for (int i = 0; i < 3; ++i)
-					obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3)));   // 2 - 10
-
-				return obj;
-			}
-
-			case 4:
-			case 5:
-			{
-				CheckTokenCount (tokens, 14);
-				CheckTokenNumbers (tokens, 1, 13);
-
-				// Quadrilateral / Conditional line
-				LDObjectPtr obj;
-
-				if (num == 4)
-					obj = LDSpawn<LDQuad>();
-				else
-					obj = LDSpawn<LDCondLine>();
-
-				obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1])));
-
-				for (int i = 0; i < 4; ++i)
-					obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3)));   // 2 - 13
-
-				return obj;
-			}
-
-			default:
-				throw QString ("Unknown line code number");
-		}
-	}
-	catch (QString& e)
-	{
-		// Strange line we couldn't parse
-		return LDSpawn<LDError> (line, e);
-	}
-}
-
-// =============================================================================
-//
-LDDocumentPtr GetDocument (QString filename)
-{
-	// Try find the file in the list of loaded files
-	LDDocumentPtr doc = FindDocument (filename);
-
-	// If it's not loaded, try open it
-	if (not doc)
-		doc = OpenDocument (filename, true, true);
-
-	return doc;
-}
-
-// =============================================================================
-//
-void LDDocument::reloadAllSubfiles()
-{
-	print ("Reloading subfiles of %1", getDisplayName());
-
-	// Go through all objects in the current file and reload the subfiles
-	for (LDObjectPtr obj : objects())
-	{
-		if (obj->type() == OBJ_Subfile)
-		{
-			LDSubfilePtr ref = obj.staticCast<LDSubfile>();
-			LDDocumentPtr fileInfo = GetDocument (ref->fileInfo()->name());
-
-			if (fileInfo != null)
-			{
-				ref->setFileInfo (fileInfo);
-			}
-			else
-			{
-				ref->replace (LDSpawn<LDError> (ref->asText(),
-					format ("Could not open %1", ref->fileInfo()->name())));
-			}
-		}
-
-		// Reparse gibberish files. It could be that they are invalid because
-		// of loading errors. Circumstances may be different now.
-		if (obj->type() == OBJ_Error)
-			obj->replace (ParseLine (obj.staticCast<LDError>()->contents()));
-	}
-
-	m_needsReCache = true;
-
-	if (self() == CurrentDocument())
-		g_win->buildObjList();
-}
-
-// =============================================================================
-//
-int LDDocument::addObject (LDObjectPtr obj)
-{
-	history()->add (new AddHistory (objects().size(), obj));
-	m_objects << obj;
-	addKnownVertices (obj);
-	obj->setDocument (self());
-	g_win->R()->compileObject (obj);
-	return getObjectCount() - 1;
-}
-
-// =============================================================================
-//
-void LDDocument::addObjects (const LDObjectList& objs)
-{
-	for (LDObjectPtr obj : objs)
-	{
-		if (obj != null)
-			addObject (obj);
-	}
-}
-
-// =============================================================================
-//
-void LDDocument::insertObj (int pos, LDObjectPtr obj)
-{
-	history()->add (new AddHistory (pos, obj));
-	m_objects.insert (pos, obj);
-	obj->setDocument (self());
-	g_win->R()->compileObject (obj);
-	
-
-#ifdef DEBUG
-	if (not isImplicit())
-		dprint ("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos);
-#endif
-}
-
-// =============================================================================
-//
-void LDDocument::addKnownVertices (LDObjectPtr obj)
-{
-	auto it = m_objectVertices.find (obj);
-
-	if (it == m_objectVertices.end())
-		it = m_objectVertices.insert (obj, QVector<Vertex>());
-	else
-		it->clear();
-
-	obj->getVertices (*it);
-	needVertexMerge();
-}
-
-// =============================================================================
-//
-void LDDocument::forgetObject (LDObjectPtr obj)
-{
-	int idx = obj->lineNumber();
-	obj->deselect();
-	assert (m_objects[idx] == obj);
-
-	if (not isImplicit() and not (flags() & DOCF_IsBeingDestroyed))
-	{
-		history()->add (new DelHistory (idx, obj));
-		m_objectVertices.remove (obj);
-	}
-
-	m_objects.removeAt (idx);
-	obj->setDocument (LDDocumentPtr());
-}
-
-// =============================================================================
-//
-bool IsSafeToCloseAll()
-{
-	for (LDDocumentPtr f : LDDocument::explicitDocuments())
-	{
-		if (not f->isSafeToClose())
-			return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-void LDDocument::setObject (int idx, LDObjectPtr obj)
-{
-	assert (idx >= 0 and idx < m_objects.size());
-
-	// Mark this change to history
-	if (not m_history->isIgnoring())
-	{
-		QString oldcode = getObject (idx)->asText();
-		QString newcode = obj->asText();
-		*m_history << new EditHistory (idx, oldcode, newcode);
-	}
-
-	m_objectVertices.remove (m_objects[idx]);
-	m_objects[idx]->deselect();
-	m_objects[idx]->setDocument (LDDocumentPtr());
-	obj->setDocument (self());
-	addKnownVertices (obj);
-	g_win->R()->compileObject (obj);
-	m_objects[idx] = obj;
-	needVertexMerge();
-}
-
-// =============================================================================
-//
-LDObjectPtr LDDocument::getObject (int pos) const
-{
-	if (m_objects.size() <= pos)
-		return LDObjectPtr();
-
-	return m_objects[pos];
-}
-
-// =============================================================================
-//
-int LDDocument::getObjectCount() const
-{
-	return objects().size();
-}
-
-// =============================================================================
-//
-bool LDDocument::hasUnsavedChanges() const
-{
-	return not isImplicit() and history()->position() != savePosition();
-}
-
-// =============================================================================
-//
-QString LDDocument::getDisplayName()
-{
-	if (not name().isEmpty())
-		return name();
-
-	if (not defaultName().isEmpty())
-		return "[" + defaultName() + "]";
-
-	return QObject::tr ("untitled");
-}
-
-// =============================================================================
-//
-void LDDocument::initializeCachedData()
-{
-	if (m_needsReCache)
-	{
-		m_vertices.clear();
-
-		for (LDObjectPtr obj : inlineContents (true, true))
-		{
-			if (obj->type() == OBJ_Subfile)
-			{
-				print ("Warning: unable to inline %1 into %2",
-					obj.staticCast<LDSubfile>()->fileInfo()->getDisplayName(),
-					getDisplayName());
-				continue;
-			}
-
-			LDPolygon* data = obj->getPolygon();
-
-			if (data != null)
-			{
-				m_polygonData << *data;
-				delete data;
-			}
-		}
-
-		m_needsReCache = false;
-	}
-
-	if (m_verticesOutdated)
-	{
-		m_objectVertices.clear();
-
-		for (LDObjectPtr obj : inlineContents (true, false))
-			addKnownVertices (obj);
-
-		mergeVertices();
-		m_verticesOutdated = false;
-	}
-
-	if (m_needVertexMerge)
-		mergeVertices();
-}
-
-// =============================================================================
-//
-void LDDocument::mergeVertices()
-{
-	m_vertices.clear();
-
-	for (QVector<Vertex> const& verts : m_objectVertices)
-		m_vertices << verts;
-
-	RemoveDuplicates (m_vertices);
-	m_needVertexMerge = false;
-}
-
-// =============================================================================
-//
-QList<LDPolygon> LDDocument::inlinePolygons()
-{
-	initializeCachedData();
-	return polygonData();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObjectList LDDocument::inlineContents (bool deep, bool renderinline)
-{
-	// Possibly substitute with logoed studs:
-	// stud.dat -> stud-logo.dat
-	// stud2.dat -> stud-logo2.dat
-	if (cfg::UseLogoStuds and renderinline)
-	{
-		// Ensure logoed studs are loaded first
-		LoadLogoStuds();
-
-		if (name() == "stud.dat" and g_logoedStud != null)
-			return g_logoedStud->inlineContents (deep, renderinline);
-		elif (name() == "stud2.dat" and g_logoedStud2 != null)
-			return g_logoedStud2->inlineContents (deep, renderinline);
-	}
-
-	LDObjectList objs, objcache;
-
-	for (LDObjectPtr obj : objects())
-	{
-		// Skip those without scemantic meaning
-		if (not obj->isScemantic())
-			continue;
-
-		// Got another sub-file reference, inline it if we're deep-inlining. If not,
-		// just add it into the objects normally. Yay, recursion!
-		if (deep == true and obj->type() == OBJ_Subfile)
-		{
-			for (LDObjectPtr otherobj : obj.staticCast<LDSubfile>()->inlineContents (deep, renderinline))
-				objs << otherobj;
-		}
-		else
-			objs << obj->createCopy();
-	}
-
-	return objs;
-}
-
-// =============================================================================
-//
-LDDocumentPtr LDDocument::current()
-{
-	return g_currentDocument;
-}
-
-// =============================================================================
-// Sets the given file as the current one on display. At some point in time this
-// was an operation completely unheard of. ;)
-//
-// TODO: f can be temporarily null. This probably should not be the case.
-// =============================================================================
-void LDDocument::setCurrent (LDDocumentPtr f)
-{
-	// Implicit files were loaded for caching purposes and must never be set
-	// current.
-	if (f != null and f->isImplicit())
-		return;
-
-	g_currentDocument = f;
-
-	if (g_win and f)
-	{
-		// A ton of stuff needs to be updated
-		g_win->updateDocumentListItem (f);
-		g_win->buildObjList();
-		g_win->updateTitle();
-		g_win->R()->setDocument (f);
-		g_win->R()->compiler()->needMerge();
-		print ("Changed file to %1", f->getDisplayName());
-	}
-}
-
-// =============================================================================
-//
-int LDDocument::countExplicitFiles()
-{
-	return g_explicitDocuments.size();
-}
-
-// =============================================================================
-// This little beauty closes the initial file that was open at first when opening
-// a new file over it.
-// =============================================================================
-void LDDocument::closeInitialFile()
-{
-	if (g_explicitDocuments.size() == 2 and
-		g_explicitDocuments[0]->name().isEmpty() and
-		not g_explicitDocuments[1]->name().isEmpty() and
-		not g_explicitDocuments[0]->hasUnsavedChanges())
-	{
-		LDDocumentPtr filetoclose = g_explicitDocuments.first();
-		filetoclose->dismiss();
-	}
-}
-
-// =============================================================================
-//
-void LoadLogoStuds()
-{
-	if (g_loadingLogoedStuds or (g_logoedStud and g_logoedStud2))
-		return;
-
-	g_loadingLogoedStuds = true;
-	g_logoedStud = OpenDocument ("stud-logo.dat", true, true);
-	g_logoedStud2 = OpenDocument ("stud2-logo.dat", true, true);
-	print (QObject::tr ("Logoed studs loaded.\n"));
-	g_loadingLogoedStuds = false;
-}
-
-// =============================================================================
-//
-void LDDocument::addToSelection (LDObjectPtr obj) // [protected]
-{
-	if (obj->isSelected())
-		return;
-
-	assert (obj->document() == self());
-	m_sel << obj;
-	g_win->R()->compileObject (obj);
-	obj->setSelected (true);
-}
-
-// =============================================================================
-//
-void LDDocument::removeFromSelection (LDObjectPtr obj) // [protected]
-{
-	if (not obj->isSelected())
-		return;
-
-	assert (obj->document() == self());
-	m_sel.removeOne (obj);
-	g_win->R()->compileObject (obj);
-	obj->setSelected (false);
-}
-
-// =============================================================================
-//
-void LDDocument::clearSelection()
-{
-	for (LDObjectPtr obj : m_sel)
-		removeFromSelection (obj);
-
-	assert (m_sel.isEmpty());
-}
-
-// =============================================================================
-//
-const LDObjectList& LDDocument::getSelection() const
-{
-	return m_sel;
-}
-
-// =============================================================================
-//
-void LDDocument::swapObjects (LDObjectPtr one, LDObjectPtr other)
-{
-	int a = m_objects.indexOf (one);
-	int b = m_objects.indexOf (other);
-	assert (a != b and a != -1 and b != -1);
-	m_objects[b] = one;
-	m_objects[a] = other;
-	addToHistory (new SwapHistory (one->id(), other->id()));
-}
-
-// =============================================================================
-//
-QString LDDocument::shortenName (QString a) // [static]
-{
-	QString shortname = Basename (a);
-	QString topdirname = Basename (Dirname (a));
-
-	if (g_specialSubdirectories.contains (topdirname))
-		shortname.prepend (topdirname + "\\");
-
-	return shortname;
-}
-
-// =============================================================================
-//
-QVector<Vertex> const& LDDocument::inlineVertices()
-{
-	initializeCachedData();
-	return m_vertices;
-}
-
-void LDDocument::redoVertices()
-{
-	m_verticesOutdated = true;
-}
-
-void LDDocument::needVertexMerge()
-{
-	m_needVertexMerge = true;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldDocument.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,1540 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QMessageBox>
+#include <QFileDialog>
+#include <QDir>
+#include <QTime>
+#include <QApplication>
+
+#include "main.h"
+#include "configuration.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "editHistory.h"
+#include "dialogs.h"
+#include "glRenderer.h"
+#include "glCompiler.h"
+#include "partDownloader.h"
+
+CFGENTRY (String, LDrawPath, "")
+CFGENTRY (List, RecentFiles, {})
+CFGENTRY (Bool, TryDownloadMissingFiles, false)
+EXTERN_CFGENTRY (String, DownloadFilePath)
+EXTERN_CFGENTRY (Bool, UseLogoStuds)
+
+static bool g_loadingMainFile = false;
+static const int g_maxRecentFiles = 10;
+static bool g_aborted = false;
+static LDDocumentPtr g_logoedStud;
+static LDDocumentPtr g_logoedStud2;
+static QList<LDDocumentWeakPtr> g_allDocuments;
+static QList<LDDocumentPtr> g_explicitDocuments;
+static LDDocumentPtr g_currentDocument;
+static bool g_loadingLogoedStuds = false;
+
+const QStringList g_specialSubdirectories ({ "s", "48", "8" });
+
+// =============================================================================
+//
+namespace LDPaths
+{
+	static QString pathError;
+
+	struct
+	{
+		QString LDConfigPath;
+		QString partsPath, primsPath;
+	} pathInfo;
+
+	void initPaths()
+	{
+		if (not tryConfigure (cfg::LDrawPath))
+		{
+			LDrawPathDialog dlg (false);
+
+			if (not dlg.exec())
+				Exit();
+
+			cfg::LDrawPath = dlg.filename();
+		}
+	}
+
+	bool tryConfigure (QString path)
+	{
+		QDir dir;
+
+		if (not dir.cd (path))
+		{
+			pathError = "Directory does not exist.";
+			return false;
+		}
+
+		QStringList mustHave = { "LDConfig.ldr", "parts", "p" };
+		QStringList contents = dir.entryList (mustHave);
+
+		if (contents.size() != mustHave.size())
+		{
+			pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/.";
+			return false;
+		}
+
+		pathInfo.partsPath = format ("%1" DIRSLASH "parts", path);
+		pathInfo.LDConfigPath = format ("%1" DIRSLASH "LDConfig.ldr", path);
+		pathInfo.primsPath = format ("%1" DIRSLASH "p", path);
+
+		return true;
+	}
+
+	// Accessors
+	QString getError()
+	{
+		return pathError;
+	}
+
+	QString ldconfig()
+	{
+		return pathInfo.LDConfigPath;
+	}
+
+	QString prims()
+	{
+		return pathInfo.primsPath;
+	}
+
+	QString parts()
+	{
+		return pathInfo.partsPath;
+	}
+}
+
+// =============================================================================
+//
+LDDocument::LDDocument (LDDocumentPtr* selfptr) :
+	m_isImplicit (true),
+	m_flags (0),
+	m_verticesOutdated (true),
+	m_needVertexMerge (true),
+	m_gldata (new LDGLData)
+{
+	*selfptr = LDDocumentPtr (this);
+	setSelf (*selfptr);
+	setSavePosition (-1);
+	setTabIndex (-1);
+	setHistory (new History);
+	history()->setDocument (*selfptr);
+	m_needsReCache = true;
+	g_allDocuments << *selfptr;
+}
+
+// =============================================================================
+//
+LDDocumentPtr LDDocument::createNew()
+{
+	LDDocumentPtr ptr;
+	new LDDocument (&ptr);
+	return ptr;
+}
+
+// =============================================================================
+//
+LDDocument::~LDDocument()
+{
+	// Don't bother during program termination
+	if (IsExiting())
+		return;
+
+	g_allDocuments.removeOne (self());
+	m_flags |= DOCF_IsBeingDestroyed;
+	delete m_history;
+	delete m_gldata;
+}
+
+// =============================================================================
+//
+void LDDocument::setImplicit (bool const& a)
+{
+	if (m_isImplicit != a)
+	{
+		m_isImplicit = a;
+
+		if (a == false)
+		{
+			g_explicitDocuments << self().toStrongRef();
+			print ("Opened %1", name());
+
+			// Implicit files are not compiled by the GL renderer. Now that this
+			// part is no longer implicit, it needs to be compiled.
+			if (g_win != null)
+				g_win->R()->compiler()->compileDocument (self());
+		}
+		else
+		{
+			g_explicitDocuments.removeOne (self().toStrongRef());
+			print ("Closed %1", name());
+		}
+
+		if (g_win != null)
+			g_win->updateDocumentList();
+
+		// If the current document just became implicit (e.g. it was 'closed'),
+		// we need to get a new current document.
+		if (current() == self() and isImplicit())
+		{
+			if (explicitDocuments().isEmpty())
+				newFile();
+			else
+				setCurrent (explicitDocuments().first());
+		}
+	}
+}
+
+// =============================================================================
+//
+QList<LDDocumentPtr> const& LDDocument::explicitDocuments()
+{
+	return g_explicitDocuments;
+}
+
+// =============================================================================
+//
+LDDocumentPtr FindDocument (QString name)
+{
+	for (LDDocumentWeakPtr weakfile : g_allDocuments)
+	{
+		if (weakfile == null)
+			continue;
+
+		LDDocumentPtr file (weakfile.toStrongRef());
+
+		if (Eq (name, file->name(), file->defaultName()))
+			return file;
+	}
+
+	return LDDocumentPtr();
+}
+
+// =============================================================================
+//
+QString Dirname (QString path)
+{
+	long lastpos = path.lastIndexOf (DIRSLASH);
+
+	if (lastpos > 0)
+		return path.left (lastpos);
+
+#ifndef _WIN32
+	if (path[0] == DIRSLASH_CHAR)
+		return DIRSLASH;
+#endif // _WIN32
+
+	return "";
+}
+
+// =============================================================================
+//
+QString Basename (QString path)
+{
+	long lastpos = path.lastIndexOf (DIRSLASH);
+
+	if (lastpos != -1)
+		return path.mid (lastpos + 1);
+
+	return path;
+}
+
+// =============================================================================
+//
+static QString FindDocumentPath (QString relpath, bool subdirs)
+{
+	QString fullPath;
+
+	// LDraw models use Windows-style path separators. If we're not on Windows,
+	// replace the path separator now before opening any files. Qt expects
+	// forward-slashes as directory separators.
+#ifndef WIN32
+	relpath.replace ("\\", "/");
+#endif // WIN32
+
+	// Try find it relative to other currently open documents. We want a file
+	// in the immediate vicinity of a current model to override stock LDraw stuff.
+	QString reltop = Basename (Dirname (relpath));
+
+	for (LDDocumentWeakPtr doc : g_allDocuments)
+	{
+		if (doc == null)
+			continue;
+
+		QString partpath = format ("%1/%2", Dirname (doc.toStrongRef()->fullPath()), relpath);
+		QFile f (partpath);
+
+		if (f.exists())
+		{
+			// ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48
+			QString proptop = Basename (Dirname (partpath));
+
+			bool bogus = false;
+
+			for (QString s : g_specialSubdirectories)
+			{
+				if ((proptop == s and reltop != s) or (reltop == s and proptop != s))
+				{
+					bogus = true;
+					break;
+				}
+			}
+
+			if (not bogus)
+				return partpath;
+		}
+	}
+
+	if (QFile::exists (relpath))
+		return relpath;
+
+	// Try with just the LDraw path first
+	fullPath = format ("%1" DIRSLASH "%2", cfg::LDrawPath, relpath);
+
+	if (QFile::exists (fullPath))
+		return fullPath;
+
+	if (subdirs)
+	{
+		// Look in sub-directories: parts and p. Also look in net_downloadpath, since that's
+		// where we download parts from the PT to.
+		for (const QString& topdir : QList<QString> ({ cfg::LDrawPath, cfg::DownloadFilePath }))
+		{
+			for (const QString& subdir : QList<QString> ({ "parts", "p" }))
+			{
+				fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath);
+
+				if (QFile::exists (fullPath))
+					return fullPath;
+			}
+		}
+	}
+
+	// Did not find the file.
+	return "";
+}
+
+// =============================================================================
+//
+QFile* OpenLDrawFile (QString relpath, bool subdirs, QString* pathpointer)
+{
+	print ("Opening %1...\n", relpath);
+	QString path = FindDocumentPath (relpath, subdirs);
+
+	if (pathpointer != null)
+		*pathpointer = path;
+
+	if (path.isEmpty())
+		return null;
+
+	QFile* fp = new QFile (path);
+
+	if (fp->open (QIODevice::ReadOnly))
+		return fp;
+
+	fp->deleteLater();
+	return null;
+}
+
+// =============================================================================
+//
+void LDFileLoader::start()
+{
+	setDone (false);
+	setProgress (0);
+	setAborted (false);
+
+	if (isOnForeground())
+	{
+		g_aborted = false;
+
+		// Show a progress dialog if we're loading the main ldDocument.here so we can
+		// show progress updates and keep the WM posted that we're still here.
+		// Of course we cannot exec() the dialog because then the dialog would
+		// block.
+		dlg = new OpenProgressDialog (g_win);
+		dlg->setNumLines (lines().size());
+		dlg->setModal (true);
+		dlg->show();
+
+		// Connect the loader in so we can show updates
+		connect (this, SIGNAL (workDone()), dlg, SLOT (accept()));
+		connect (dlg, SIGNAL (rejected()), this, SLOT (abort()));
+	}
+	else
+		dlg = null;
+
+	// Begin working
+	work (0);
+}
+
+// =============================================================================
+//
+void LDFileLoader::work (int i)
+{
+	// User wishes to abort, so stop here now.
+	if (isAborted())
+	{
+		for (LDObjectPtr obj : m_objects)
+			obj->destroy();
+
+		m_objects.clear();
+		setDone (true);
+		return;
+	}
+
+	// Parse up to 300 lines per iteration
+	int max = i + 300;
+
+	for (; i < max and i < (int) lines().size(); ++i)
+	{
+		QString line = lines()[i];
+
+		// Trim the trailing newline
+		QChar c;
+
+		while (line.endsWith ("\n") or line.endsWith ("\r"))
+			line.chop (1);
+
+		LDObjectPtr obj = ParseLine (line);
+
+		// Check for parse errors and warn about tthem
+		if (obj->type() == OBJ_Error)
+		{
+			print ("Couldn't parse line #%1: %2",
+				progress() + 1, obj.staticCast<LDError>()->reason());
+
+			if (warnings() != null)
+				(*warnings())++;
+		}
+
+		m_objects << obj;
+		setProgress (i);
+
+		// If we have a dialog pointer, update the progress now
+		if (isOnForeground())
+			dlg->updateProgress (i);
+	}
+
+	// If we're done now, tell the environment we're done and stop.
+	if (i >= ((int) lines().size()) - 1)
+	{
+		emit workDone();
+		setDone (true);
+		return;
+	}
+
+	// Otherwise, continue, by recursing back.
+	if (not isDone())
+	{
+		// If we have a dialog to show progress output to, we cannot just call
+		// work() again immediately as the dialog needs some processor cycles as
+		// well. Thus, take a detour through the event loop by using the
+		// meta-object system.
+		//
+		// This terminates the loop here and control goes back to the function
+		// which called the file loader. It will keep processing the event loop
+		// until we're ready (see loadFileContents), thus the event loop will
+		// eventually catch the invokation we throw here and send us back. Though
+		// it's not technically recursion anymore, more like a for loop. :P
+		if (isOnForeground())
+			QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i));
+		else
+			work (i);
+	}
+}
+
+// =============================================================================
+//
+void LDFileLoader::abort()
+{
+	setAborted (true);
+
+	if (isOnForeground())
+		g_aborted = true;
+}
+
+// =============================================================================
+//
+LDObjectList LoadFileContents (QFile* fp, int* numWarnings, bool* ok)
+{
+	QStringList lines;
+	LDObjectList objs;
+
+	if (numWarnings)
+		*numWarnings = 0;
+
+	// Read in the lines
+	while (not fp->atEnd())
+		lines << QString::fromUtf8 (fp->readLine());
+
+	LDFileLoader* loader = new LDFileLoader;
+	loader->setWarnings (numWarnings);
+	loader->setLines (lines);
+	loader->setOnForeground (g_loadingMainFile);
+	loader->start();
+
+	// After start() returns, if the loader isn't done yet, it's delaying
+	// its next iteration through the event loop. We need to catch this here
+	// by telling the event loop to tick, which will tick the file loader again.
+	// We keep doing this until the file loader is ready.
+	while (not loader->isDone())
+		qApp->processEvents();
+
+	// If we wanted the success value, supply that now
+	if (ok)
+		*ok = not loader->isAborted();
+
+	objs = loader->objects();
+	delete loader;
+	return objs;
+}
+
+// =============================================================================
+//
+LDDocumentPtr OpenDocument (QString path, bool search, bool implicit, LDDocumentPtr fileToOverride)
+{
+	// Convert the file name to lowercase when searching because some parts contain subfile
+	// subfile references with uppercase file names. I'll assume here that the library will always
+	// use lowercase file names for the part files.
+	QFile* fp;
+	QString fullpath;
+
+	if (search)
+	{
+		fp = OpenLDrawFile (path.toLower(), true, &fullpath);
+	}
+	else
+	{
+		fp = new QFile (path);
+		fullpath = path;
+
+		if (not fp->open (QIODevice::ReadOnly))
+		{
+			delete fp;
+			return LDDocumentPtr();
+		}
+	}
+
+	if (not fp)
+		return LDDocumentPtr();
+
+	LDDocumentPtr load = (fileToOverride != null ? fileToOverride : LDDocument::createNew());
+	load->setImplicit (implicit);
+	load->setFullPath (fullpath);
+	load->setName (LDDocument::shortenName (load->fullPath()));
+
+	// Loading the file shouldn't count as actual edits to the document.
+	load->history()->setIgnoring (true);
+
+	int numWarnings;
+	bool ok;
+	LDObjectList objs = LoadFileContents (fp, &numWarnings, &ok);
+	fp->close();
+	fp->deleteLater();
+
+	if (not ok)
+	{
+		load->dismiss();
+		return LDDocumentPtr();
+	}
+
+	load->addObjects (objs);
+
+	if (g_loadingMainFile)
+	{
+		LDDocument::setCurrent (load);
+		g_win->R()->setDocument (load);
+		print (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings);
+	}
+
+	load->history()->setIgnoring (false);
+	return load;
+}
+
+// =============================================================================
+//
+bool LDDocument::isSafeToClose()
+{
+	using msgbox = QMessageBox;
+	setlocale (LC_ALL, "C");
+
+	// If we have unsaved changes, warn and give the option of saving.
+	if (hasUnsavedChanges())
+	{
+		QString message = format (QObject::tr ("There are unsaved changes to %1. Should it be saved?"), getDisplayName());
+
+		int button = msgbox::question (g_win, QObject::tr ("Unsaved Changes"), message,
+			(msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel);
+
+		switch (button)
+		{
+			case msgbox::Yes:
+			{
+				// If we don't have a file path yet, we have to ask the user for one.
+				if (name().length() == 0)
+				{
+					QString newpath = QFileDialog::getSaveFileName (g_win, QObject::tr ("Save As"),
+						CurrentDocument()->name(), QObject::tr ("LDraw files (*.dat *.ldr)"));
+
+					if (newpath.length() == 0)
+						return false;
+
+					setName (newpath);
+				}
+
+				if (not save())
+				{
+					message = format (QObject::tr ("Failed to save %1 (%2)\nDo you still want to close?"),
+						name(), strerror (errno));
+
+					if (msgbox::critical (g_win, QObject::tr ("Save Failure"), message,
+						(msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No)
+					{
+						return false;
+					}
+				}
+				break;
+			}
+
+			case msgbox::Cancel:
+				return false;
+
+			default:
+				break;
+		}
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+void CloseAllDocuments()
+{
+	for (LDDocumentPtr file : g_explicitDocuments)
+		file->dismiss();
+}
+
+// =============================================================================
+//
+void newFile()
+{
+	// Create a new anonymous file and set it to our current
+	LDDocumentPtr f = LDDocument::createNew();
+	f->setName ("");
+	f->setImplicit (false);
+	LDDocument::setCurrent (f);
+	LDDocument::closeInitialFile();
+	g_win->R()->setDocument (f);
+	g_win->doFullRefresh();
+	g_win->updateTitle();
+	g_win->updateActions();
+}
+
+// =============================================================================
+//
+void AddRecentFile (QString path)
+{
+	int idx = cfg::RecentFiles.indexOf (path);
+
+	// If this file already is in the list, pop it out.
+	if (idx != -1)
+	{
+		if (idx == cfg::RecentFiles.size() - 1)
+			return; // first recent file - abort and do nothing
+
+		cfg::RecentFiles.removeAt (idx);
+	}
+
+	// If there's too many recent files, drop one out.
+	while (cfg::RecentFiles.size() > (g_maxRecentFiles - 1))
+		cfg::RecentFiles.removeAt (0);
+
+	// Add the file
+	cfg::RecentFiles << path;
+
+	Config::Save();
+	g_win->updateRecentFilesMenu();
+}
+
+// =============================================================================
+// Open an LDraw file and set it as the main model
+// =============================================================================
+void OpenMainModel (QString path)
+{
+	// If there's already a file with the same name, this file must replace it.
+	LDDocumentPtr documentToReplace;
+	LDDocumentPtr file;
+	QString shortName = LDDocument::shortenName (path);
+
+	for (LDDocumentWeakPtr doc : g_allDocuments)
+	{
+		if (doc != null and doc.toStrongRef()->name() == shortName)
+		{
+			documentToReplace = doc;
+			break;
+		}
+	}
+
+	// We cannot open this file if the document this would replace is not
+	// safe to close.
+	if (documentToReplace != null and not documentToReplace->isSafeToClose())
+		return;
+
+	g_loadingMainFile = true;
+
+	// If we're replacing an existing document, clear the document and
+	// make it ready for being loaded to.
+	if (documentToReplace != null)
+	{
+		file = documentToReplace;
+		file->clear();
+	}
+
+	file = OpenDocument (path, false, false, file);
+
+	if (file == null)
+	{
+		if (not g_aborted)
+		{
+			// Tell the user loading failed.
+			setlocale (LC_ALL, "C");
+			Critical (format (QObject::tr ("Failed to open %1: %2"), path, strerror (errno)));
+		}
+
+		g_loadingMainFile = false;
+		return;
+	}
+
+	file->setImplicit (false);
+
+	// 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;
+
+	// If there were problems loading subfile references, try see if we can find these
+	// files on the parts tracker.
+	QStringList unknowns;
+
+	for (LDObjectPtr obj : file->objects())
+	{
+		if (obj->type() != OBJ_Error or obj.staticCast<LDError>()->fileReferenced().isEmpty())
+			continue;
+
+		unknowns << obj.staticCast<LDError>()->fileReferenced();
+	}
+
+	if (cfg::TryDownloadMissingFiles and not unknowns.isEmpty())
+	{
+		PartDownloader dl;
+
+		if (dl.checkValidPath())
+		{
+			dl.setSource (PartDownloader::PartsTracker);
+			dl.setPrimaryFile (file);
+
+			for (QString const& unknown : unknowns)
+				dl.downloadFromPartsTracker (unknown);
+
+			dl.exec();
+			dl.checkIfFinished();
+			file->reloadAllSubfiles();
+		}
+	}
+}
+
+// =============================================================================
+//
+bool LDDocument::save (QString path, int64* sizeptr)
+{
+	if (isImplicit())
+		return false;
+
+	if (not path.length())
+		path = fullPath();
+
+	// If the second object in the list holds the file name, update that now.
+	LDObjectPtr nameObject = getObject (1);
+
+	if (nameObject != null and nameObject->type() == OBJ_Comment)
+	{
+		LDCommentPtr nameComment = nameObject.staticCast<LDComment>();
+
+		if (nameComment->text().left (6) == "Name: ")
+		{
+			QString newname = shortenName (path);
+			nameComment->setText (format ("Name: %1", newname));
+			g_win->buildObjList();
+		}
+	}
+
+	QByteArray data;
+
+	if (sizeptr != null)
+		*sizeptr = 0;
+
+	// 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 (LDObjectPtr obj : objects())
+	{
+		QByteArray subdata ((obj->asText() + "\r\n").toUtf8());
+		data.append (subdata);
+
+		if (sizeptr != null)
+			*sizeptr += subdata.size();
+	}
+
+	QFile f (path);
+
+	if (not f.open (QIODevice::WriteOnly))
+		return false;
+
+	f.write (data);
+	f.close();
+
+	// We have successfully saved, update the save position now.
+	setSavePosition (history()->position());
+	setFullPath (path);
+	setName (shortenName (path));
+
+	g_win->updateDocumentListItem (self().toStrongRef());
+	g_win->updateTitle();
+	return true;
+}
+
+// =============================================================================
+//
+void LDDocument::clear()
+{
+	for (LDObjectPtr obj : objects())
+		forgetObject (obj);
+}
+
+// =============================================================================
+//
+static void CheckTokenCount (const QStringList& tokens, int num)
+{
+	if (tokens.size() != num)
+		throw QString (format ("Bad amount of tokens, expected %1, got %2", num, tokens.size()));
+}
+
+// =============================================================================
+//
+static void CheckTokenNumbers (const QStringList& tokens, int min, int max)
+{
+	bool ok;
+
+	QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+");
+
+	for (int i = min; i <= max; ++i)
+	{
+		// Check for floating point
+		tokens[i].toDouble (&ok);
+		if (ok)
+			return;
+
+		// Check hex
+		if (tokens[i].startsWith ("0x"))
+		{
+			tokens[i].mid (2).toInt (&ok, 16);
+
+			if (ok)
+				return;
+		}
+
+		// Check scientific notation, e.g. 7.99361e-15
+		if (scient.exactMatch (tokens[i]))
+			return;
+
+		throw QString (format ("Token #%1 was `%2`, expected a number (matched length: %3)",
+			(i + 1), tokens[i], scient.matchedLength()));
+	}
+}
+
+// =============================================================================
+//
+static Vertex ParseVertex (QStringList& s, const int n)
+{
+	Vertex v;
+	v.apply ([&] (Axis ax, double& a) { a = s[n + ax].toDouble(); });
+	return v;
+}
+
+static int32 StringToNumber (QString a, bool* ok = null)
+{
+	int base = 10;
+
+	if (a.startsWith ("0x"))
+	{
+		a.remove (0, 2);
+		base = 16;
+	}
+
+	return a.toLong (ok, base);
+}
+
+// =============================================================================
+// 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.
+// =============================================================================
+LDObjectPtr ParseLine (QString line)
+{
+	try
+	{
+		QStringList tokens = line.split (" ", QString::SkipEmptyParts);
+
+		if (tokens.size() <= 0)
+		{
+			// Line was empty, or only consisted of whitespace
+			return LDSpawn<LDEmpty>();
+		}
+
+		if (tokens[0].length() != 1 or not tokens[0][0].isDigit())
+			throw QString ("Illogical line code");
+
+		int num = tokens[0][0].digitValue();
+
+		switch (num)
+		{
+			case 0:
+			{
+				// Comment
+				QString commentText (line.mid (line.indexOf ("0") + 2));
+				QString commentTextSimplified (commentText.simplified());
+
+				// Handle BFC statements
+				if (tokens.size() > 2 and tokens[1] == "BFC")
+				{
+					for_enum (BFCStatement, i)
+					{
+						if (commentTextSimplified == format ("BFC %1",
+							LDBFC::StatementStrings[int (i)]))
+						{
+							return LDSpawn<LDBFC> (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.
+					if (commentTextSimplified == "BFC CERTIFY INVERTNEXT")
+						return LDSpawn<LDBFC> (BFCStatement::InvertNext);
+					elif (commentTextSimplified == "BFC CERTIFY CLIP")
+						return LDSpawn<LDBFC> (BFCStatement::Clip);
+					elif (commentTextSimplified == "BFC CERTIFY NOCLIP")
+						return LDSpawn<LDBFC> (BFCStatement::NoClip);
+				}
+
+				if (tokens.size() > 2 and tokens[1] == "!LDFORGE")
+				{
+					// Handle LDForge-specific types, they're embedded into comments too
+					if (tokens[2] == "OVERLAY")
+					{
+						CheckTokenCount (tokens, 9);
+						CheckTokenNumbers (tokens, 5, 8);
+
+						LDOverlayPtr obj = LDSpawn<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:
+				LDCommentPtr obj = LDSpawn<LDComment>();
+				obj->setText (commentText);
+				return obj;
+			}
+
+			case 1:
+			{
+				// Subfile
+				CheckTokenCount (tokens, 15);
+				CheckTokenNumbers (tokens, 1, 13);
+
+				// Try open the file. Disable g_loadingMainFile temporarily since we're
+				// not loading the main file now, but the subfile in question.
+				bool tmp = g_loadingMainFile;
+				g_loadingMainFile = false;
+				LDDocumentPtr load = GetDocument (tokens[14]);
+				g_loadingMainFile = tmp;
+
+				// If we cannot open the file, mark it an error. Note we cannot use LDParseError
+				// here because the error object needs the document reference.
+				if (not load)
+				{
+					LDErrorPtr obj = LDSpawn<LDError> (line, format ("Could not open %1", tokens[14]));
+					obj->setFileReferenced (tokens[14]);
+					return obj;
+				}
+
+				LDSubfilePtr obj = LDSpawn<LDSubfile>();
+				obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1])));
+				obj->setPosition (ParseVertex (tokens, 2));  // 2 - 4
+
+				Matrix transform;
+
+				for (int i = 0; i < 9; ++i)
+					transform[i] = tokens[i + 5].toDouble(); // 5 - 13
+
+				obj->setTransform (transform);
+				obj->setFileInfo (load);
+				return obj;
+			}
+
+			case 2:
+			{
+				CheckTokenCount (tokens, 8);
+				CheckTokenNumbers (tokens, 1, 7);
+
+				// Line
+				LDLinePtr obj (LDSpawn<LDLine>());
+				obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1])));
+
+				for (int i = 0; i < 2; ++i)
+					obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3)));   // 2 - 7
+
+				return obj;
+			}
+
+			case 3:
+			{
+				CheckTokenCount (tokens, 11);
+				CheckTokenNumbers (tokens, 1, 10);
+
+				// Triangle
+				LDTrianglePtr obj (LDSpawn<LDTriangle>());
+				obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1])));
+
+				for (int i = 0; i < 3; ++i)
+					obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3)));   // 2 - 10
+
+				return obj;
+			}
+
+			case 4:
+			case 5:
+			{
+				CheckTokenCount (tokens, 14);
+				CheckTokenNumbers (tokens, 1, 13);
+
+				// Quadrilateral / Conditional line
+				LDObjectPtr obj;
+
+				if (num == 4)
+					obj = LDSpawn<LDQuad>();
+				else
+					obj = LDSpawn<LDCondLine>();
+
+				obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1])));
+
+				for (int i = 0; i < 4; ++i)
+					obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3)));   // 2 - 13
+
+				return obj;
+			}
+
+			default:
+				throw QString ("Unknown line code number");
+		}
+	}
+	catch (QString& e)
+	{
+		// Strange line we couldn't parse
+		return LDSpawn<LDError> (line, e);
+	}
+}
+
+// =============================================================================
+//
+LDDocumentPtr GetDocument (QString filename)
+{
+	// Try find the file in the list of loaded files
+	LDDocumentPtr doc = FindDocument (filename);
+
+	// If it's not loaded, try open it
+	if (not doc)
+		doc = OpenDocument (filename, true, true);
+
+	return doc;
+}
+
+// =============================================================================
+//
+void LDDocument::reloadAllSubfiles()
+{
+	print ("Reloading subfiles of %1", getDisplayName());
+
+	// Go through all objects in the current file and reload the subfiles
+	for (LDObjectPtr obj : objects())
+	{
+		if (obj->type() == OBJ_Subfile)
+		{
+			LDSubfilePtr ref = obj.staticCast<LDSubfile>();
+			LDDocumentPtr fileInfo = GetDocument (ref->fileInfo()->name());
+
+			if (fileInfo != null)
+			{
+				ref->setFileInfo (fileInfo);
+			}
+			else
+			{
+				ref->replace (LDSpawn<LDError> (ref->asText(),
+					format ("Could not open %1", ref->fileInfo()->name())));
+			}
+		}
+
+		// Reparse gibberish files. It could be that they are invalid because
+		// of loading errors. Circumstances may be different now.
+		if (obj->type() == OBJ_Error)
+			obj->replace (ParseLine (obj.staticCast<LDError>()->contents()));
+	}
+
+	m_needsReCache = true;
+
+	if (self() == CurrentDocument())
+		g_win->buildObjList();
+}
+
+// =============================================================================
+//
+int LDDocument::addObject (LDObjectPtr obj)
+{
+	history()->add (new AddHistory (objects().size(), obj));
+	m_objects << obj;
+	addKnownVertices (obj);
+	obj->setDocument (self());
+	g_win->R()->compileObject (obj);
+	return getObjectCount() - 1;
+}
+
+// =============================================================================
+//
+void LDDocument::addObjects (const LDObjectList& objs)
+{
+	for (LDObjectPtr obj : objs)
+	{
+		if (obj != null)
+			addObject (obj);
+	}
+}
+
+// =============================================================================
+//
+void LDDocument::insertObj (int pos, LDObjectPtr obj)
+{
+	history()->add (new AddHistory (pos, obj));
+	m_objects.insert (pos, obj);
+	obj->setDocument (self());
+	g_win->R()->compileObject (obj);
+	
+
+#ifdef DEBUG
+	if (not isImplicit())
+		dprint ("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos);
+#endif
+}
+
+// =============================================================================
+//
+void LDDocument::addKnownVertices (LDObjectPtr obj)
+{
+	auto it = m_objectVertices.find (obj);
+
+	if (it == m_objectVertices.end())
+		it = m_objectVertices.insert (obj, QVector<Vertex>());
+	else
+		it->clear();
+
+	obj->getVertices (*it);
+	needVertexMerge();
+}
+
+// =============================================================================
+//
+void LDDocument::forgetObject (LDObjectPtr obj)
+{
+	int idx = obj->lineNumber();
+	obj->deselect();
+	assert (m_objects[idx] == obj);
+
+	if (not isImplicit() and not (flags() & DOCF_IsBeingDestroyed))
+	{
+		history()->add (new DelHistory (idx, obj));
+		m_objectVertices.remove (obj);
+	}
+
+	m_objects.removeAt (idx);
+	obj->setDocument (LDDocumentPtr());
+}
+
+// =============================================================================
+//
+bool IsSafeToCloseAll()
+{
+	for (LDDocumentPtr f : LDDocument::explicitDocuments())
+	{
+		if (not f->isSafeToClose())
+			return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+void LDDocument::setObject (int idx, LDObjectPtr obj)
+{
+	assert (idx >= 0 and idx < m_objects.size());
+
+	// Mark this change to history
+	if (not m_history->isIgnoring())
+	{
+		QString oldcode = getObject (idx)->asText();
+		QString newcode = obj->asText();
+		*m_history << new EditHistory (idx, oldcode, newcode);
+	}
+
+	m_objectVertices.remove (m_objects[idx]);
+	m_objects[idx]->deselect();
+	m_objects[idx]->setDocument (LDDocumentPtr());
+	obj->setDocument (self());
+	addKnownVertices (obj);
+	g_win->R()->compileObject (obj);
+	m_objects[idx] = obj;
+	needVertexMerge();
+}
+
+// =============================================================================
+//
+LDObjectPtr LDDocument::getObject (int pos) const
+{
+	if (m_objects.size() <= pos)
+		return LDObjectPtr();
+
+	return m_objects[pos];
+}
+
+// =============================================================================
+//
+int LDDocument::getObjectCount() const
+{
+	return objects().size();
+}
+
+// =============================================================================
+//
+bool LDDocument::hasUnsavedChanges() const
+{
+	return not isImplicit() and history()->position() != savePosition();
+}
+
+// =============================================================================
+//
+QString LDDocument::getDisplayName()
+{
+	if (not name().isEmpty())
+		return name();
+
+	if (not defaultName().isEmpty())
+		return "[" + defaultName() + "]";
+
+	return QObject::tr ("untitled");
+}
+
+// =============================================================================
+//
+void LDDocument::initializeCachedData()
+{
+	if (m_needsReCache)
+	{
+		m_vertices.clear();
+
+		for (LDObjectPtr obj : inlineContents (true, true))
+		{
+			if (obj->type() == OBJ_Subfile)
+			{
+				print ("Warning: unable to inline %1 into %2",
+					obj.staticCast<LDSubfile>()->fileInfo()->getDisplayName(),
+					getDisplayName());
+				continue;
+			}
+
+			LDPolygon* data = obj->getPolygon();
+
+			if (data != null)
+			{
+				m_polygonData << *data;
+				delete data;
+			}
+		}
+
+		m_needsReCache = false;
+	}
+
+	if (m_verticesOutdated)
+	{
+		m_objectVertices.clear();
+
+		for (LDObjectPtr obj : inlineContents (true, false))
+			addKnownVertices (obj);
+
+		mergeVertices();
+		m_verticesOutdated = false;
+	}
+
+	if (m_needVertexMerge)
+		mergeVertices();
+}
+
+// =============================================================================
+//
+void LDDocument::mergeVertices()
+{
+	m_vertices.clear();
+
+	for (QVector<Vertex> const& verts : m_objectVertices)
+		m_vertices << verts;
+
+	RemoveDuplicates (m_vertices);
+	m_needVertexMerge = false;
+}
+
+// =============================================================================
+//
+QList<LDPolygon> LDDocument::inlinePolygons()
+{
+	initializeCachedData();
+	return polygonData();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDObjectList LDDocument::inlineContents (bool deep, bool renderinline)
+{
+	// Possibly substitute with logoed studs:
+	// stud.dat -> stud-logo.dat
+	// stud2.dat -> stud-logo2.dat
+	if (cfg::UseLogoStuds and renderinline)
+	{
+		// Ensure logoed studs are loaded first
+		LoadLogoStuds();
+
+		if (name() == "stud.dat" and g_logoedStud != null)
+			return g_logoedStud->inlineContents (deep, renderinline);
+		elif (name() == "stud2.dat" and g_logoedStud2 != null)
+			return g_logoedStud2->inlineContents (deep, renderinline);
+	}
+
+	LDObjectList objs, objcache;
+
+	for (LDObjectPtr obj : objects())
+	{
+		// Skip those without scemantic meaning
+		if (not obj->isScemantic())
+			continue;
+
+		// Got another sub-file reference, inline it if we're deep-inlining. If not,
+		// just add it into the objects normally. Yay, recursion!
+		if (deep == true and obj->type() == OBJ_Subfile)
+		{
+			for (LDObjectPtr otherobj : obj.staticCast<LDSubfile>()->inlineContents (deep, renderinline))
+				objs << otherobj;
+		}
+		else
+			objs << obj->createCopy();
+	}
+
+	return objs;
+}
+
+// =============================================================================
+//
+LDDocumentPtr LDDocument::current()
+{
+	return g_currentDocument;
+}
+
+// =============================================================================
+// Sets the given file as the current one on display. At some point in time this
+// was an operation completely unheard of. ;)
+//
+// TODO: f can be temporarily null. This probably should not be the case.
+// =============================================================================
+void LDDocument::setCurrent (LDDocumentPtr f)
+{
+	// Implicit files were loaded for caching purposes and must never be set
+	// current.
+	if (f != null and f->isImplicit())
+		return;
+
+	g_currentDocument = f;
+
+	if (g_win and f)
+	{
+		// A ton of stuff needs to be updated
+		g_win->updateDocumentListItem (f);
+		g_win->buildObjList();
+		g_win->updateTitle();
+		g_win->R()->setDocument (f);
+		g_win->R()->compiler()->needMerge();
+		print ("Changed file to %1", f->getDisplayName());
+	}
+}
+
+// =============================================================================
+//
+int LDDocument::countExplicitFiles()
+{
+	return g_explicitDocuments.size();
+}
+
+// =============================================================================
+// This little beauty closes the initial file that was open at first when opening
+// a new file over it.
+// =============================================================================
+void LDDocument::closeInitialFile()
+{
+	if (g_explicitDocuments.size() == 2 and
+		g_explicitDocuments[0]->name().isEmpty() and
+		not g_explicitDocuments[1]->name().isEmpty() and
+		not g_explicitDocuments[0]->hasUnsavedChanges())
+	{
+		LDDocumentPtr filetoclose = g_explicitDocuments.first();
+		filetoclose->dismiss();
+	}
+}
+
+// =============================================================================
+//
+void LoadLogoStuds()
+{
+	if (g_loadingLogoedStuds or (g_logoedStud and g_logoedStud2))
+		return;
+
+	g_loadingLogoedStuds = true;
+	g_logoedStud = OpenDocument ("stud-logo.dat", true, true);
+	g_logoedStud2 = OpenDocument ("stud2-logo.dat", true, true);
+	print (QObject::tr ("Logoed studs loaded.\n"));
+	g_loadingLogoedStuds = false;
+}
+
+// =============================================================================
+//
+void LDDocument::addToSelection (LDObjectPtr obj) // [protected]
+{
+	if (obj->isSelected())
+		return;
+
+	assert (obj->document() == self());
+	m_sel << obj;
+	g_win->R()->compileObject (obj);
+	obj->setSelected (true);
+}
+
+// =============================================================================
+//
+void LDDocument::removeFromSelection (LDObjectPtr obj) // [protected]
+{
+	if (not obj->isSelected())
+		return;
+
+	assert (obj->document() == self());
+	m_sel.removeOne (obj);
+	g_win->R()->compileObject (obj);
+	obj->setSelected (false);
+}
+
+// =============================================================================
+//
+void LDDocument::clearSelection()
+{
+	for (LDObjectPtr obj : m_sel)
+		removeFromSelection (obj);
+
+	assert (m_sel.isEmpty());
+}
+
+// =============================================================================
+//
+const LDObjectList& LDDocument::getSelection() const
+{
+	return m_sel;
+}
+
+// =============================================================================
+//
+void LDDocument::swapObjects (LDObjectPtr one, LDObjectPtr other)
+{
+	int a = m_objects.indexOf (one);
+	int b = m_objects.indexOf (other);
+	assert (a != b and a != -1 and b != -1);
+	m_objects[b] = one;
+	m_objects[a] = other;
+	addToHistory (new SwapHistory (one->id(), other->id()));
+}
+
+// =============================================================================
+//
+QString LDDocument::shortenName (QString a) // [static]
+{
+	QString shortname = Basename (a);
+	QString topdirname = Basename (Dirname (a));
+
+	if (g_specialSubdirectories.contains (topdirname))
+		shortname.prepend (topdirname + "\\");
+
+	return shortname;
+}
+
+// =============================================================================
+//
+QVector<Vertex> const& LDDocument::inlineVertices()
+{
+	initializeCachedData();
+	return m_vertices;
+}
+
+void LDDocument::redoVertices()
+{
+	m_verticesOutdated = true;
+}
+
+void LDDocument::needVertexMerge()
+{
+	m_needVertexMerge = true;
+}
--- a/src/ldObject.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,925 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-
-#include "main.h"
-#include "ldObject.h"
-#include "ldDocument.h"
-#include "miscallenous.h"
-#include "mainWindow.h"
-#include "editHistory.h"
-#include "glRenderer.h"
-#include "colors.h"
-#include "glCompiler.h"
-
-CFGENTRY (String, DefaultName, "")
-CFGENTRY (String, DefaultUser, "")
-CFGENTRY (Bool, UseCALicense, true)
-
-// List of all LDObjects
-QMap<long, LDObjectWeakPtr>	g_allObjects;
-static int32						g_idcursor = 1; // 0 shalt be null
-static constexpr int32				g_maxID = (1 << 24);
-
-#define LDOBJ_DEFAULT_CTOR(T,BASE) \
-	T :: T (LDObjectPtr* selfptr) : \
-		BASE (selfptr) {}
-
-// =============================================================================
-// LDObject constructors
-//
-LDObject::LDObject (LDObjectPtr* selfptr) :
-	m_isHidden (false),
-	m_isSelected (false),
-	m_isDestructed (false),
-	qObjListEntry (null)
-{
-	*selfptr = LDObjectPtr (this, [](LDObject* obj){ obj->finalDelete(); });
-	memset (m_coords, 0, sizeof m_coords);
-	m_self = selfptr->toWeakRef();
-	chooseID();
-	g_allObjects[id()] = self();
-	setRandomColor (QColor::fromHsv (rand() % 360, rand() % 256, rand() % 96 + 128));
-}
-
-LDSubfile::LDSubfile (LDObjectPtr* selfptr) :
-	LDMatrixObject (selfptr) {}
-
-LDOBJ_DEFAULT_CTOR (LDEmpty, LDObject)
-LDOBJ_DEFAULT_CTOR (LDError, LDObject)
-LDOBJ_DEFAULT_CTOR (LDLine, LDObject)
-LDOBJ_DEFAULT_CTOR (LDTriangle, LDObject)
-LDOBJ_DEFAULT_CTOR (LDCondLine, LDLine)
-LDOBJ_DEFAULT_CTOR (LDQuad, LDObject)
-LDOBJ_DEFAULT_CTOR (LDOverlay, LDObject)
-LDOBJ_DEFAULT_CTOR (LDBFC, LDObject)
-LDOBJ_DEFAULT_CTOR (LDComment, LDObject)
-
-// =============================================================================
-//
-void LDObject::chooseID()
-{
-	// If this is the first pass we can just use a global ID counter for each
-	// unique object. Let's hope that nobody goes to create 17 million objects
-	// anytime soon.
-	if (g_idcursor < g_maxID)
-	{
-		setID (g_idcursor++);
-		return;
-	}
-
-	// In case someone does, we cannot really continue execution. We must abort,
-	// give the user a chance to save their documents though.
-	Critical ("Created too many objects. Execution cannot continue. You have a "
-		"chance to save any changes to documents, then restart.");
-	(void) IsSafeToCloseAll();
-	Exit();
-}
-
-// =============================================================================
-//
-void LDObject::setVertexCoord (int i, Axis ax, double value)
-{
-	Vertex v = vertex (i);
-	v.setCoordinate (ax, value);
-	setVertex (i, v);
-}
-
-// =============================================================================
-//
-QString LDComment::asText() const
-{
-	return format ("0 %1", text());
-}
-
-// =============================================================================
-//
-QString LDSubfile::asText() const
-{
-	QString val = format ("1 %1 %2 ", color(), position());
-	val += transform().toString();
-	val += ' ';
-	val += fileInfo()->name();
-	return val;
-}
-
-// =============================================================================
-//
-QString LDLine::asText() const
-{
-	QString val = format ("2 %1", color());
-
-	for (int i = 0; i < 2; ++i)
-		val += format (" %1", vertex (i));
-
-	return val;
-}
-
-// =============================================================================
-//
-QString LDTriangle::asText() const
-{
-	QString val = format ("3 %1", color());
-
-	for (int i = 0; i < 3; ++i)
-		val += format (" %1", vertex (i));
-
-	return val;
-}
-
-// =============================================================================
-//
-QString LDQuad::asText() const
-{
-	QString val = format ("4 %1", color());
-
-	for (int i = 0; i < 4; ++i)
-		val += format (" %1", vertex (i));
-
-	return val;
-}
-
-// =============================================================================
-//
-QString LDCondLine::asText() const
-{
-	QString val = format ("5 %1", color());
-
-	// Add the coordinates
-	for (int i = 0; i < 4; ++i)
-		val += format (" %1", vertex (i));
-
-	return val;
-}
-
-// =============================================================================
-//
-QString LDError::asText() const
-{
-	return contents();
-}
-
-// =============================================================================
-//
-QString LDEmpty::asText() const
-{
-	return "";
-}
-
-// =============================================================================
-//
-const char* LDBFC::StatementStrings[] =
-{
-	"CERTIFY CCW",
-	"CCW",
-	"CERTIFY CW",
-	"CW",
-	"NOCERTIFY",
-	"INVERTNEXT",
-	"CLIP",
-	"CLIP CCW",
-	"CLIP CW",
-	"NOCLIP",
-};
-
-QString LDBFC::asText() const
-{
-	return format ("0 BFC %1", StatementStrings[int (m_statement)]);
-}
-
-// =============================================================================
-//
-QList<LDTrianglePtr> LDQuad::splitToTriangles()
-{
-	// Create the two triangles based on this quadrilateral:
-	// 0---3       0---3    3
-	// |   |       |  /    /|
-	// |   |  ==>  | /    / |
-	// |   |       |/    /  |
-	// 1---2       1    1---2
-	LDTrianglePtr tri1 (LDSpawn<LDTriangle> (vertex (0), vertex (1), vertex (3)));
-	LDTrianglePtr tri2 (LDSpawn<LDTriangle> (vertex (1), vertex (2), vertex (3)));
-
-	// The triangles also inherit the quad's color
-	tri1->setColor (color());
-	tri2->setColor (color());
-
-	return {tri1, tri2};
-}
-
-// =============================================================================
-//
-void LDObject::replace (LDObjectPtr other)
-{
-	long idx = lineNumber();
-	assert (idx != -1);
-
-	// Replace the instance of the old object with the new object
-	document().toStrongRef()->setObject (idx, other);
-
-	// Remove the old object
-	destroy();
-}
-
-// =============================================================================
-//
-void LDObject::swap (LDObjectPtr other)
-{
-	assert (document() == other->document());
-	document().toStrongRef()->swapObjects (self(), other);
-}
-
-// =============================================================================
-//
-LDLine::LDLine (LDObjectPtr* selfptr, Vertex v1, Vertex v2) :
-	LDObject (selfptr)
-{
-	setVertex (0, v1);
-	setVertex (1, v2);
-}
-
-// =============================================================================
-//
-LDTriangle::LDTriangle (LDObjectPtr* selfptr, const Vertex& v1, const Vertex& v2, const Vertex& v3) :
-	LDObject (selfptr)
-{
-	setVertex (0, v1);
-	setVertex (1, v2);
-	setVertex (2, v3);
-}
-
-// =============================================================================
-//
-LDQuad::LDQuad (LDObjectPtr* selfptr, const Vertex& v1, const Vertex& v2,
-	const Vertex& v3, const Vertex& v4) :
-	LDObject (selfptr)
-{
-	setVertex (0, v1);
-	setVertex (1, v2);
-	setVertex (2, v3);
-	setVertex (3, v4);
-}
-
-// =============================================================================
-//
-LDCondLine::LDCondLine (LDObjectPtr* selfptr, const Vertex& v0, const Vertex& v1,
-						const Vertex& v2, const Vertex& v3) :
-	LDLine (selfptr)
-{
-	setVertex (0, v0);
-	setVertex (1, v1);
-	setVertex (2, v2);
-	setVertex (3, v3);
-}
-
-// =============================================================================
-//
-LDObject::~LDObject() {}
-
-// =============================================================================
-//
-void LDObject::destroy()
-{
-	// Don't bother during program termination
-	if (IsExiting() or isDestructed())
-		return;
-
-	// If this object was selected, unselect it now
-	if (isSelected() and document() != null)
-		deselect();
-
-	// If this object was associated to a file, remove it off it now
-	if (document() != null)
-		document().toStrongRef()->forgetObject (self());
-
-	// Delete the GL lists
-	if (g_win != null)
-		g_win->R()->forgetObject (self());
-
-	// Remove this object from the list of LDObjects
-	g_allObjects.erase (g_allObjects.find (id()));
-	setDestructed (true);
-}
-
-//
-// Deletes the object. Only the shared pointer is to call this!
-//
-void LDObject::finalDelete()
-{
-	if (not isDestructed())
-		destroy();
-
-	delete this;
-}
-
-// =============================================================================
-//
-void LDObject::setDocument (const LDDocumentWeakPtr& a)
-{
-	m_document = a;
-
-	if (a == null)
-		setSelected (false);
-}
-
-// =============================================================================
-//
-static void TransformObject (LDObjectPtr obj, Matrix transform, Vertex pos, LDColor parentcolor)
-{
-	switch (obj->type())
-	{
-		case OBJ_Line:
-		case OBJ_CondLine:
-		case OBJ_Triangle:
-		case OBJ_Quad:
-			for (int i = 0; i < obj->numVertices(); ++i)
-			{
-				Vertex v = obj->vertex (i);
-				v.transform (transform, pos);
-				obj->setVertex (i, v);
-			}
-
-			break;
-
-		case OBJ_Subfile:
-		{
-			LDSubfilePtr ref = qSharedPointerCast<LDSubfile> (obj);
-			Matrix newMatrix = transform * ref->transform();
-			Vertex newpos = ref->position();
-			newpos.transform (transform, pos);
-			ref->setPosition (newpos);
-			ref->setTransform (newMatrix);
-			break;
-		}
-
-		default:
-			break;
-	}
-
-	if (obj->color() == MainColor())
-		obj->setColor (parentcolor);
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-LDObjectList LDSubfile::inlineContents (bool deep, bool render)
-{
-	LDObjectList objs = fileInfo()->inlineContents (deep, render);
-
-	// Transform the objects
-	for (LDObjectPtr obj : objs)
-	{
-		// assert (obj->type() != OBJ_Subfile);
-		// Set the parent now so we know what inlined the object.
-		obj->setParent (self());
-		TransformObject (obj, transform(), position(), color());
-	}
-
-	return objs;
-}
-
-// =============================================================================
-//
-LDPolygon* LDObject::getPolygon()
-{
-	LDObjectType ot = type();
-	int num = (ot == OBJ_Line)		? 2
-			: (ot == OBJ_Triangle)	? 3
-			: (ot == OBJ_Quad)		? 4
-			: (ot == OBJ_CondLine)	? 5
-			: 0;
-
-	if (num == 0)
-		return null;
-
-	LDPolygon* data = new LDPolygon;
-	data->id = id();
-	data->num = num;
-	data->color = color().index();
-
-	for (int i = 0; i < data->numVertices(); ++i)
-		data->vertices[i] = vertex (i);
-
-	return data;
-}
-
-// =============================================================================
-//
-QList<LDPolygon> LDSubfile::inlinePolygons()
-{
-	QList<LDPolygon> data = fileInfo()->inlinePolygons();
-
-	for (LDPolygon& entry : data)
-	{
-		for (int i = 0; i < entry.numVertices(); ++i)
-			entry.vertices[i].transform (transform(), position());
-	}
-
-	return data;
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-long LDObject::lineNumber() const
-{
-	assert (document() != null);
-
-	for (int i = 0; i < document().toStrongRef()->getObjectCount(); ++i)
-	{
-		if (document().toStrongRef()->getObject (i) == this)
-			return i;
-	}
-
-	return -1;
-}
-
-// =============================================================================
-//
-void LDObject::moveObjects (LDObjectList objs, const bool up)
-{
-	if (objs.isEmpty())
-		return;
-
-	// If we move down, we need to iterate the array in reverse order.
-	long const start = up ? 0 : (objs.size() - 1);
-	long const end = up ? objs.size() : -1;
-	long const incr = up ? 1 : -1;
-	LDObjectList objsToCompile;
-	LDDocumentPtr file = objs[0]->document();
-
-	for (long i = start; i != end; i += incr)
-	{
-		LDObjectPtr obj = objs[i];
-
-		long const idx = obj->lineNumber();
-		long const target = idx + (up ? -1 : 1);
-
-		if ((up and idx == 0) or (not up and idx == (long) file->objects().size() - 1l))
-		{
-			// 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 (LDObjectPtr obj : objsToCompile)
-		g_win->R()->compileObject (obj);
-}
-
-// =============================================================================
-//
-QString LDObject::typeName (LDObjectType type)
-{
-	return LDObject::getDefault (type)->typeName();
-}
-
-// =============================================================================
-//
-QString LDObject::describeObjects (const LDObjectList& objs)
-{
-	QString text;
-
-	if (objs.isEmpty())
-		return "nothing"; // :)
-
-	for (LDObjectType objType = OBJ_FirstType; objType < OBJ_NumTypes; ++objType)
-	{
-		int count = 0;
-
-		for (LDObjectPtr obj : objs)
-		{
-			if (obj->type() == objType)
-				count++;
-		}
-
-		if (count == 0)
-			continue;
-
-		if (not text.isEmpty())
-			text += ", ";
-
-		QString noun = format ("%1%2", typeName (objType), Plural (count));
-		text += format ("%1 %2", count, noun);
-	}
-
-	return text;
-}
-
-// =============================================================================
-//
-LDObjectPtr LDObject::topLevelParent()
-{
-	if (parent() == null)
-		return self();
-
-	LDObjectWeakPtr it (self());
-
-	while (it.toStrongRef()->parent() != null)
-		it = it.toStrongRef()->parent();
-
-	return it.toStrongRef();
-}
-
-// =============================================================================
-//
-LDObjectPtr LDObject::next() const
-{
-	long idx = lineNumber();
-	assert (idx != -1);
-
-	if (idx == (long) document().toStrongRef()->getObjectCount() - 1)
-		return LDObjectPtr();
-
-	return document().toStrongRef()->getObject (idx + 1);
-}
-
-// =============================================================================
-//
-LDObjectPtr LDObject::previous() const
-{
-	long idx = lineNumber();
-	assert (idx != -1);
-
-	if (idx == 0)
-		return LDObjectPtr();
-
-	return document().toStrongRef()->getObject (idx - 1);
-}
-
-// =============================================================================
-//
-bool LDObject::previousIsInvertnext (LDBFCPtr& ptr)
-{
-	LDObjectPtr prev (previous());
-
-	if (prev != null and prev->type() == OBJ_BFC and
-		prev.staticCast<LDBFC>()->statement() == BFCStatement::InvertNext)
-	{
-		ptr = prev.staticCast<LDBFC>();
-		return true;
-	}
-
-	return false;
-}
-
-// =============================================================================
-//
-void LDObject::move (Vertex vect)
-{
-	if (hasMatrix())
-	{
-		LDMatrixObjectPtr mo = self().toStrongRef().dynamicCast<LDMatrixObject>();
-		mo->setPosition (mo->position() + vect);
-	}
-	else
-	{
-		for (int i = 0; i < numVertices(); ++i)
-			setVertex (i, vertex (i) + vect);
-	}
-}
-
-// =============================================================================
-//
-LDObjectPtr LDObject::getDefault (const LDObjectType type)
-{
-	switch (type)
-	{
-		case OBJ_Comment:		return LDSpawn<LDComment>();
-		case OBJ_BFC:			return LDSpawn<LDBFC>();
-		case OBJ_Line:			return LDSpawn<LDLine>();
-		case OBJ_CondLine:		return LDSpawn<LDCondLine>();
-		case OBJ_Subfile:		return LDSpawn<LDSubfile>();
-		case OBJ_Triangle:		return LDSpawn<LDTriangle>();
-		case OBJ_Quad:			return LDSpawn<LDQuad>();
-		case OBJ_Empty:			return LDSpawn<LDEmpty>();
-		case OBJ_Error:			return LDSpawn<LDError>();
-		case OBJ_Overlay:		return LDSpawn<LDOverlay>();
-		case OBJ_NumTypes:		assert (false);
-	}
-	return LDObjectPtr();
-}
-
-// =============================================================================
-//
-void LDObject::invert() {}
-void LDBFC::invert() {}
-void LDEmpty::invert() {}
-void LDComment::invert() {}
-void LDError::invert() {}
-
-// =============================================================================
-//
-void LDTriangle::invert()
-{
-	// Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1.
-	// Thus, we swap 1 and 2.
-	Vertex tmp = vertex (1);
-	setVertex (1, vertex (2));
-	setVertex (2, tmp);
-
-	return;
-}
-
-// =============================================================================
-//
-void LDQuad::invert()
-{
-	// Quad: 0 -> 1 -> 2 -> 3
-	// rev:  0 -> 3 -> 2 -> 1
-	// Thus, we swap 1 and 3.
-	Vertex tmp = vertex (1);
-	setVertex (1, vertex (3));
-	setVertex (3, tmp);
-}
-
-// =============================================================================
-//
-void LDSubfile::invert()
-{
-	if (document() == null)
-		return;
-
-	// Check whether subfile is flat
-	int axisSet = (1 << X) | (1 << Y) | (1 << Z);
-	LDObjectList objs = fileInfo()->inlineContents (true, false);
-
-	for (LDObjectPtr obj : objs)
-	{
-		for (int i = 0; i < obj->numVertices(); ++i)
-		{
-			Vertex const& vrt = obj->vertex (i);
-
-			if (axisSet & (1 << X) and vrt.x() != 0.0)
-				axisSet &= ~(1 << X);
-
-			if (axisSet & (1 << Y) and vrt.y() != 0.0)
-				axisSet &= ~(1 << Y);
-
-			if (axisSet & (1 << Z) and vrt.z() != 0.0)
-				axisSet &= ~(1 << Z);
-		}
-
-		if (axisSet == 0)
-			break;
-	}
-
-	if (axisSet != 0)
-	{
-		// Subfile has all vertices zero on one specific plane, so it is flat.
-		// Let's flip it.
-		Matrix matrixModifier = IdentityMatrix;
-
-		if (axisSet & (1 << X))
-			matrixModifier[0] = -1;
-
-		if (axisSet & (1 << Y))
-			matrixModifier[4] = -1;
-
-		if (axisSet & (1 << Z))
-			matrixModifier[8] = -1;
-
-		setTransform (transform() * matrixModifier);
-		return;
-	}
-
-	// Subfile is not flat. Resort to invertnext.
-	int idx = lineNumber();
-
-	if (idx > 0)
-	{
-		LDBFCPtr bfc = previous().dynamicCast<LDBFC>();
-
-		if (not bfc.isNull() and bfc->statement() == BFCStatement::InvertNext)
-		{
-			// This is prefixed with an invertnext, thus remove it.
-			bfc->destroy();
-			return;
-		}
-	}
-
-	// Not inverted, thus prefix it with a new invertnext.
-	document().toStrongRef()->insertObj (idx, LDSpawn<LDBFC> (BFCStatement::InvertNext));
-}
-
-// =============================================================================
-//
-void LDLine::invert()
-{
-	// For lines, we swap the vertices.
-	Vertex tmp = vertex (0);
-	setVertex (0, vertex (1));
-	setVertex (1, tmp);
-}
-
-// =============================================================================
-//
-void LDCondLine::invert()
-{
-	// I don't think that a conditional line's control points need to be
-	// swapped, do they?
-	Vertex tmp = vertex (0);
-	setVertex (0, vertex (1));
-	setVertex (1, tmp);
-}
-
-// =============================================================================
-//
-LDLinePtr LDCondLine::toEdgeLine()
-{
-	LDLinePtr replacement (LDSpawn<LDLine>());
-
-	for (int i = 0; i < replacement->numVertices(); ++i)
-		replacement->setVertex (i, vertex (i));
-
-	replacement->setColor (color());
-
-	replace (replacement);
-	return replacement;
-}
-
-// =============================================================================
-//
-LDObjectPtr LDObject::fromID (int id)
-{
-	auto it = g_allObjects.find (id);
-
-	if (it != g_allObjects.end())
-		return *it;
-
-	return LDObjectPtr();
-}
-
-// =============================================================================
-//
-QString LDOverlay::asText() const
-{
-	return format ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6",
-		fileName(), camera(), x(), y(), width(), height());
-}
-
-void LDOverlay::invert() {}
-
-// =============================================================================
-//
-// Hook the set accessors of certain properties to this changeProperty function.
-// It takes care of history management so we can capture low-level changes, this
-// makes history stuff work out of the box.
-//
-template<typename T>
-static void changeProperty (LDObjectPtr obj, T* ptr, const T& val)
-{
-	long idx;
-
-	if (*ptr == val)
-		return;
-
-	if (obj->document() != null and (idx = obj->lineNumber()) != -1)
-	{
-		QString before = obj->asText();
-		*ptr = val;
-		QString after = obj->asText();
-
-		if (before != after)
-		{
-			obj->document().toStrongRef()->addToHistory (new EditHistory (idx, before, after));
-			g_win->R()->compileObject (obj);
-			CurrentDocument()->redoVertices();
-		}
-	}
-	else
-		*ptr = val;
-}
-
-// =============================================================================
-//
-void LDObject::setColor (LDColor const& val)
-{
-	changeProperty (self(), &m_color, val);
-}
-
-// =============================================================================
-//
-const Vertex& LDObject::vertex (int i) const
-{
-	return m_coords[i];
-}
-
-// =============================================================================
-//
-void LDObject::setVertex (int i, const Vertex& vert)
-{
-	changeProperty (self(), &m_coords[i], vert);
-}
-
-// =============================================================================
-//
-void LDMatrixObject::setPosition (const Vertex& a)
-{
-	changeProperty (self(), &m_position, a);
-}
-
-// =============================================================================
-//
-void LDMatrixObject::setTransform (const Matrix& val)
-{
-	changeProperty (self(), &m_transform, val);
-}
-
-// =============================================================================
-//
-void LDObject::select()
-{
-	assert (document() != null);
-	document().toStrongRef()->addToSelection (self());
-
-	// If this object is inverted with INVERTNEXT, pick the INVERTNEXT as well.
-	/*
-	LDBFCPtr invertnext;
-
-	if (previousIsInvertnext (invertnext))
-		invertnext->select();
-	*/
-}
-
-// =============================================================================
-//
-void LDObject::deselect()
-{
-	assert (document() != null);
-	document().toStrongRef()->removeFromSelection (self());
-
-	// If this object is inverted with INVERTNEXT, deselect the INVERTNEXT as well.
-	LDBFCPtr invertnext;
-
-	if (previousIsInvertnext (invertnext))
-		invertnext->deselect();
-}
-
-// =============================================================================
-//
-QString PreferredLicenseText()
-{
-	return (cfg::UseCALicense ? CALicenseText : "");
-}
-
-// =============================================================================
-//
-LDObjectPtr LDObject::createCopy() const
-{
-	LDObjectPtr copy = ParseLine (asText());
-	return copy;
-}
-
-// =============================================================================
-//
-void LDSubfile::setFileInfo (const LDDocumentPtr& a)
-{
-	changeProperty (self(), &m_fileInfo, a);
-
-	// If it's an immediate subfile reference (i.e. this subfile belongs in an
-	// explicit file), we need to pre-compile the GL polygons for the document
-	// if they don't exist already.
-	if (a != null and
-		a->isImplicit() == false and
-		a->polygonData().isEmpty())
-	{
-		a->initializeCachedData();
-	}
-};
-
-void LDObject::getVertices (QVector<Vertex>& verts) const
-{
-	for (int i = 0; i < numVertices(); ++i)
-		verts << vertex (i);
-}
-
-void LDSubfile::getVertices (QVector<Vertex>& verts) const
-{
-	verts << fileInfo()->inlineVertices();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldObject.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,925 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "main.h"
+#include "ldObject.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "editHistory.h"
+#include "glRenderer.h"
+#include "colors.h"
+#include "glCompiler.h"
+
+CFGENTRY (String, DefaultName, "")
+CFGENTRY (String, DefaultUser, "")
+CFGENTRY (Bool, UseCALicense, true)
+
+// List of all LDObjects
+QMap<long, LDObjectWeakPtr>	g_allObjects;
+static int32						g_idcursor = 1; // 0 shalt be null
+static constexpr int32				g_maxID = (1 << 24);
+
+#define LDOBJ_DEFAULT_CTOR(T,BASE) \
+	T :: T (LDObjectPtr* selfptr) : \
+		BASE (selfptr) {}
+
+// =============================================================================
+// LDObject constructors
+//
+LDObject::LDObject (LDObjectPtr* selfptr) :
+	m_isHidden (false),
+	m_isSelected (false),
+	m_isDestructed (false),
+	qObjListEntry (null)
+{
+	*selfptr = LDObjectPtr (this, [](LDObject* obj){ obj->finalDelete(); });
+	memset (m_coords, 0, sizeof m_coords);
+	m_self = selfptr->toWeakRef();
+	chooseID();
+	g_allObjects[id()] = self();
+	setRandomColor (QColor::fromHsv (rand() % 360, rand() % 256, rand() % 96 + 128));
+}
+
+LDSubfile::LDSubfile (LDObjectPtr* selfptr) :
+	LDMatrixObject (selfptr) {}
+
+LDOBJ_DEFAULT_CTOR (LDEmpty, LDObject)
+LDOBJ_DEFAULT_CTOR (LDError, LDObject)
+LDOBJ_DEFAULT_CTOR (LDLine, LDObject)
+LDOBJ_DEFAULT_CTOR (LDTriangle, LDObject)
+LDOBJ_DEFAULT_CTOR (LDCondLine, LDLine)
+LDOBJ_DEFAULT_CTOR (LDQuad, LDObject)
+LDOBJ_DEFAULT_CTOR (LDOverlay, LDObject)
+LDOBJ_DEFAULT_CTOR (LDBFC, LDObject)
+LDOBJ_DEFAULT_CTOR (LDComment, LDObject)
+
+// =============================================================================
+//
+void LDObject::chooseID()
+{
+	// If this is the first pass we can just use a global ID counter for each
+	// unique object. Let's hope that nobody goes to create 17 million objects
+	// anytime soon.
+	if (g_idcursor < g_maxID)
+	{
+		setID (g_idcursor++);
+		return;
+	}
+
+	// In case someone does, we cannot really continue execution. We must abort,
+	// give the user a chance to save their documents though.
+	Critical ("Created too many objects. Execution cannot continue. You have a "
+		"chance to save any changes to documents, then restart.");
+	(void) IsSafeToCloseAll();
+	Exit();
+}
+
+// =============================================================================
+//
+void LDObject::setVertexCoord (int i, Axis ax, double value)
+{
+	Vertex v = vertex (i);
+	v.setCoordinate (ax, value);
+	setVertex (i, v);
+}
+
+// =============================================================================
+//
+QString LDComment::asText() const
+{
+	return format ("0 %1", text());
+}
+
+// =============================================================================
+//
+QString LDSubfile::asText() const
+{
+	QString val = format ("1 %1 %2 ", color(), position());
+	val += transform().toString();
+	val += ' ';
+	val += fileInfo()->name();
+	return val;
+}
+
+// =============================================================================
+//
+QString LDLine::asText() const
+{
+	QString val = format ("2 %1", color());
+
+	for (int i = 0; i < 2; ++i)
+		val += format (" %1", vertex (i));
+
+	return val;
+}
+
+// =============================================================================
+//
+QString LDTriangle::asText() const
+{
+	QString val = format ("3 %1", color());
+
+	for (int i = 0; i < 3; ++i)
+		val += format (" %1", vertex (i));
+
+	return val;
+}
+
+// =============================================================================
+//
+QString LDQuad::asText() const
+{
+	QString val = format ("4 %1", color());
+
+	for (int i = 0; i < 4; ++i)
+		val += format (" %1", vertex (i));
+
+	return val;
+}
+
+// =============================================================================
+//
+QString LDCondLine::asText() const
+{
+	QString val = format ("5 %1", color());
+
+	// Add the coordinates
+	for (int i = 0; i < 4; ++i)
+		val += format (" %1", vertex (i));
+
+	return val;
+}
+
+// =============================================================================
+//
+QString LDError::asText() const
+{
+	return contents();
+}
+
+// =============================================================================
+//
+QString LDEmpty::asText() const
+{
+	return "";
+}
+
+// =============================================================================
+//
+const char* LDBFC::StatementStrings[] =
+{
+	"CERTIFY CCW",
+	"CCW",
+	"CERTIFY CW",
+	"CW",
+	"NOCERTIFY",
+	"INVERTNEXT",
+	"CLIP",
+	"CLIP CCW",
+	"CLIP CW",
+	"NOCLIP",
+};
+
+QString LDBFC::asText() const
+{
+	return format ("0 BFC %1", StatementStrings[int (m_statement)]);
+}
+
+// =============================================================================
+//
+QList<LDTrianglePtr> LDQuad::splitToTriangles()
+{
+	// Create the two triangles based on this quadrilateral:
+	// 0---3       0---3    3
+	// |   |       |  /    /|
+	// |   |  ==>  | /    / |
+	// |   |       |/    /  |
+	// 1---2       1    1---2
+	LDTrianglePtr tri1 (LDSpawn<LDTriangle> (vertex (0), vertex (1), vertex (3)));
+	LDTrianglePtr tri2 (LDSpawn<LDTriangle> (vertex (1), vertex (2), vertex (3)));
+
+	// The triangles also inherit the quad's color
+	tri1->setColor (color());
+	tri2->setColor (color());
+
+	return {tri1, tri2};
+}
+
+// =============================================================================
+//
+void LDObject::replace (LDObjectPtr other)
+{
+	long idx = lineNumber();
+	assert (idx != -1);
+
+	// Replace the instance of the old object with the new object
+	document().toStrongRef()->setObject (idx, other);
+
+	// Remove the old object
+	destroy();
+}
+
+// =============================================================================
+//
+void LDObject::swap (LDObjectPtr other)
+{
+	assert (document() == other->document());
+	document().toStrongRef()->swapObjects (self(), other);
+}
+
+// =============================================================================
+//
+LDLine::LDLine (LDObjectPtr* selfptr, Vertex v1, Vertex v2) :
+	LDObject (selfptr)
+{
+	setVertex (0, v1);
+	setVertex (1, v2);
+}
+
+// =============================================================================
+//
+LDTriangle::LDTriangle (LDObjectPtr* selfptr, const Vertex& v1, const Vertex& v2, const Vertex& v3) :
+	LDObject (selfptr)
+{
+	setVertex (0, v1);
+	setVertex (1, v2);
+	setVertex (2, v3);
+}
+
+// =============================================================================
+//
+LDQuad::LDQuad (LDObjectPtr* selfptr, const Vertex& v1, const Vertex& v2,
+	const Vertex& v3, const Vertex& v4) :
+	LDObject (selfptr)
+{
+	setVertex (0, v1);
+	setVertex (1, v2);
+	setVertex (2, v3);
+	setVertex (3, v4);
+}
+
+// =============================================================================
+//
+LDCondLine::LDCondLine (LDObjectPtr* selfptr, const Vertex& v0, const Vertex& v1,
+						const Vertex& v2, const Vertex& v3) :
+	LDLine (selfptr)
+{
+	setVertex (0, v0);
+	setVertex (1, v1);
+	setVertex (2, v2);
+	setVertex (3, v3);
+}
+
+// =============================================================================
+//
+LDObject::~LDObject() {}
+
+// =============================================================================
+//
+void LDObject::destroy()
+{
+	// Don't bother during program termination
+	if (IsExiting() or isDestructed())
+		return;
+
+	// If this object was selected, unselect it now
+	if (isSelected() and document() != null)
+		deselect();
+
+	// If this object was associated to a file, remove it off it now
+	if (document() != null)
+		document().toStrongRef()->forgetObject (self());
+
+	// Delete the GL lists
+	if (g_win != null)
+		g_win->R()->forgetObject (self());
+
+	// Remove this object from the list of LDObjects
+	g_allObjects.erase (g_allObjects.find (id()));
+	setDestructed (true);
+}
+
+//
+// Deletes the object. Only the shared pointer is to call this!
+//
+void LDObject::finalDelete()
+{
+	if (not isDestructed())
+		destroy();
+
+	delete this;
+}
+
+// =============================================================================
+//
+void LDObject::setDocument (const LDDocumentWeakPtr& a)
+{
+	m_document = a;
+
+	if (a == null)
+		setSelected (false);
+}
+
+// =============================================================================
+//
+static void TransformObject (LDObjectPtr obj, Matrix transform, Vertex pos, LDColor parentcolor)
+{
+	switch (obj->type())
+	{
+		case OBJ_Line:
+		case OBJ_CondLine:
+		case OBJ_Triangle:
+		case OBJ_Quad:
+			for (int i = 0; i < obj->numVertices(); ++i)
+			{
+				Vertex v = obj->vertex (i);
+				v.transform (transform, pos);
+				obj->setVertex (i, v);
+			}
+
+			break;
+
+		case OBJ_Subfile:
+		{
+			LDSubfilePtr ref = qSharedPointerCast<LDSubfile> (obj);
+			Matrix newMatrix = transform * ref->transform();
+			Vertex newpos = ref->position();
+			newpos.transform (transform, pos);
+			ref->setPosition (newpos);
+			ref->setTransform (newMatrix);
+			break;
+		}
+
+		default:
+			break;
+	}
+
+	if (obj->color() == MainColor())
+		obj->setColor (parentcolor);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDObjectList LDSubfile::inlineContents (bool deep, bool render)
+{
+	LDObjectList objs = fileInfo()->inlineContents (deep, render);
+
+	// Transform the objects
+	for (LDObjectPtr obj : objs)
+	{
+		// assert (obj->type() != OBJ_Subfile);
+		// Set the parent now so we know what inlined the object.
+		obj->setParent (self());
+		TransformObject (obj, transform(), position(), color());
+	}
+
+	return objs;
+}
+
+// =============================================================================
+//
+LDPolygon* LDObject::getPolygon()
+{
+	LDObjectType ot = type();
+	int num = (ot == OBJ_Line)		? 2
+			: (ot == OBJ_Triangle)	? 3
+			: (ot == OBJ_Quad)		? 4
+			: (ot == OBJ_CondLine)	? 5
+			: 0;
+
+	if (num == 0)
+		return null;
+
+	LDPolygon* data = new LDPolygon;
+	data->id = id();
+	data->num = num;
+	data->color = color().index();
+
+	for (int i = 0; i < data->numVertices(); ++i)
+		data->vertices[i] = vertex (i);
+
+	return data;
+}
+
+// =============================================================================
+//
+QList<LDPolygon> LDSubfile::inlinePolygons()
+{
+	QList<LDPolygon> data = fileInfo()->inlinePolygons();
+
+	for (LDPolygon& entry : data)
+	{
+		for (int i = 0; i < entry.numVertices(); ++i)
+			entry.vertices[i].transform (transform(), position());
+	}
+
+	return data;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+long LDObject::lineNumber() const
+{
+	assert (document() != null);
+
+	for (int i = 0; i < document().toStrongRef()->getObjectCount(); ++i)
+	{
+		if (document().toStrongRef()->getObject (i) == this)
+			return i;
+	}
+
+	return -1;
+}
+
+// =============================================================================
+//
+void LDObject::moveObjects (LDObjectList objs, const bool up)
+{
+	if (objs.isEmpty())
+		return;
+
+	// If we move down, we need to iterate the array in reverse order.
+	long const start = up ? 0 : (objs.size() - 1);
+	long const end = up ? objs.size() : -1;
+	long const incr = up ? 1 : -1;
+	LDObjectList objsToCompile;
+	LDDocumentPtr file = objs[0]->document();
+
+	for (long i = start; i != end; i += incr)
+	{
+		LDObjectPtr obj = objs[i];
+
+		long const idx = obj->lineNumber();
+		long const target = idx + (up ? -1 : 1);
+
+		if ((up and idx == 0) or (not up and idx == (long) file->objects().size() - 1l))
+		{
+			// 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 (LDObjectPtr obj : objsToCompile)
+		g_win->R()->compileObject (obj);
+}
+
+// =============================================================================
+//
+QString LDObject::typeName (LDObjectType type)
+{
+	return LDObject::getDefault (type)->typeName();
+}
+
+// =============================================================================
+//
+QString LDObject::describeObjects (const LDObjectList& objs)
+{
+	QString text;
+
+	if (objs.isEmpty())
+		return "nothing"; // :)
+
+	for (LDObjectType objType = OBJ_FirstType; objType < OBJ_NumTypes; ++objType)
+	{
+		int count = 0;
+
+		for (LDObjectPtr obj : objs)
+		{
+			if (obj->type() == objType)
+				count++;
+		}
+
+		if (count == 0)
+			continue;
+
+		if (not text.isEmpty())
+			text += ", ";
+
+		QString noun = format ("%1%2", typeName (objType), Plural (count));
+		text += format ("%1 %2", count, noun);
+	}
+
+	return text;
+}
+
+// =============================================================================
+//
+LDObjectPtr LDObject::topLevelParent()
+{
+	if (parent() == null)
+		return self();
+
+	LDObjectWeakPtr it (self());
+
+	while (it.toStrongRef()->parent() != null)
+		it = it.toStrongRef()->parent();
+
+	return it.toStrongRef();
+}
+
+// =============================================================================
+//
+LDObjectPtr LDObject::next() const
+{
+	long idx = lineNumber();
+	assert (idx != -1);
+
+	if (idx == (long) document().toStrongRef()->getObjectCount() - 1)
+		return LDObjectPtr();
+
+	return document().toStrongRef()->getObject (idx + 1);
+}
+
+// =============================================================================
+//
+LDObjectPtr LDObject::previous() const
+{
+	long idx = lineNumber();
+	assert (idx != -1);
+
+	if (idx == 0)
+		return LDObjectPtr();
+
+	return document().toStrongRef()->getObject (idx - 1);
+}
+
+// =============================================================================
+//
+bool LDObject::previousIsInvertnext (LDBFCPtr& ptr)
+{
+	LDObjectPtr prev (previous());
+
+	if (prev != null and prev->type() == OBJ_BFC and
+		prev.staticCast<LDBFC>()->statement() == BFCStatement::InvertNext)
+	{
+		ptr = prev.staticCast<LDBFC>();
+		return true;
+	}
+
+	return false;
+}
+
+// =============================================================================
+//
+void LDObject::move (Vertex vect)
+{
+	if (hasMatrix())
+	{
+		LDMatrixObjectPtr mo = self().toStrongRef().dynamicCast<LDMatrixObject>();
+		mo->setPosition (mo->position() + vect);
+	}
+	else
+	{
+		for (int i = 0; i < numVertices(); ++i)
+			setVertex (i, vertex (i) + vect);
+	}
+}
+
+// =============================================================================
+//
+LDObjectPtr LDObject::getDefault (const LDObjectType type)
+{
+	switch (type)
+	{
+		case OBJ_Comment:		return LDSpawn<LDComment>();
+		case OBJ_BFC:			return LDSpawn<LDBFC>();
+		case OBJ_Line:			return LDSpawn<LDLine>();
+		case OBJ_CondLine:		return LDSpawn<LDCondLine>();
+		case OBJ_Subfile:		return LDSpawn<LDSubfile>();
+		case OBJ_Triangle:		return LDSpawn<LDTriangle>();
+		case OBJ_Quad:			return LDSpawn<LDQuad>();
+		case OBJ_Empty:			return LDSpawn<LDEmpty>();
+		case OBJ_Error:			return LDSpawn<LDError>();
+		case OBJ_Overlay:		return LDSpawn<LDOverlay>();
+		case OBJ_NumTypes:		assert (false);
+	}
+	return LDObjectPtr();
+}
+
+// =============================================================================
+//
+void LDObject::invert() {}
+void LDBFC::invert() {}
+void LDEmpty::invert() {}
+void LDComment::invert() {}
+void LDError::invert() {}
+
+// =============================================================================
+//
+void LDTriangle::invert()
+{
+	// Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1.
+	// Thus, we swap 1 and 2.
+	Vertex tmp = vertex (1);
+	setVertex (1, vertex (2));
+	setVertex (2, tmp);
+
+	return;
+}
+
+// =============================================================================
+//
+void LDQuad::invert()
+{
+	// Quad: 0 -> 1 -> 2 -> 3
+	// rev:  0 -> 3 -> 2 -> 1
+	// Thus, we swap 1 and 3.
+	Vertex tmp = vertex (1);
+	setVertex (1, vertex (3));
+	setVertex (3, tmp);
+}
+
+// =============================================================================
+//
+void LDSubfile::invert()
+{
+	if (document() == null)
+		return;
+
+	// Check whether subfile is flat
+	int axisSet = (1 << X) | (1 << Y) | (1 << Z);
+	LDObjectList objs = fileInfo()->inlineContents (true, false);
+
+	for (LDObjectPtr obj : objs)
+	{
+		for (int i = 0; i < obj->numVertices(); ++i)
+		{
+			Vertex const& vrt = obj->vertex (i);
+
+			if (axisSet & (1 << X) and vrt.x() != 0.0)
+				axisSet &= ~(1 << X);
+
+			if (axisSet & (1 << Y) and vrt.y() != 0.0)
+				axisSet &= ~(1 << Y);
+
+			if (axisSet & (1 << Z) and vrt.z() != 0.0)
+				axisSet &= ~(1 << Z);
+		}
+
+		if (axisSet == 0)
+			break;
+	}
+
+	if (axisSet != 0)
+	{
+		// Subfile has all vertices zero on one specific plane, so it is flat.
+		// Let's flip it.
+		Matrix matrixModifier = IdentityMatrix;
+
+		if (axisSet & (1 << X))
+			matrixModifier[0] = -1;
+
+		if (axisSet & (1 << Y))
+			matrixModifier[4] = -1;
+
+		if (axisSet & (1 << Z))
+			matrixModifier[8] = -1;
+
+		setTransform (transform() * matrixModifier);
+		return;
+	}
+
+	// Subfile is not flat. Resort to invertnext.
+	int idx = lineNumber();
+
+	if (idx > 0)
+	{
+		LDBFCPtr bfc = previous().dynamicCast<LDBFC>();
+
+		if (not bfc.isNull() and bfc->statement() == BFCStatement::InvertNext)
+		{
+			// This is prefixed with an invertnext, thus remove it.
+			bfc->destroy();
+			return;
+		}
+	}
+
+	// Not inverted, thus prefix it with a new invertnext.
+	document().toStrongRef()->insertObj (idx, LDSpawn<LDBFC> (BFCStatement::InvertNext));
+}
+
+// =============================================================================
+//
+void LDLine::invert()
+{
+	// For lines, we swap the vertices.
+	Vertex tmp = vertex (0);
+	setVertex (0, vertex (1));
+	setVertex (1, tmp);
+}
+
+// =============================================================================
+//
+void LDCondLine::invert()
+{
+	// I don't think that a conditional line's control points need to be
+	// swapped, do they?
+	Vertex tmp = vertex (0);
+	setVertex (0, vertex (1));
+	setVertex (1, tmp);
+}
+
+// =============================================================================
+//
+LDLinePtr LDCondLine::toEdgeLine()
+{
+	LDLinePtr replacement (LDSpawn<LDLine>());
+
+	for (int i = 0; i < replacement->numVertices(); ++i)
+		replacement->setVertex (i, vertex (i));
+
+	replacement->setColor (color());
+
+	replace (replacement);
+	return replacement;
+}
+
+// =============================================================================
+//
+LDObjectPtr LDObject::fromID (int id)
+{
+	auto it = g_allObjects.find (id);
+
+	if (it != g_allObjects.end())
+		return *it;
+
+	return LDObjectPtr();
+}
+
+// =============================================================================
+//
+QString LDOverlay::asText() const
+{
+	return format ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6",
+		fileName(), camera(), x(), y(), width(), height());
+}
+
+void LDOverlay::invert() {}
+
+// =============================================================================
+//
+// Hook the set accessors of certain properties to this changeProperty function.
+// It takes care of history management so we can capture low-level changes, this
+// makes history stuff work out of the box.
+//
+template<typename T>
+static void changeProperty (LDObjectPtr obj, T* ptr, const T& val)
+{
+	long idx;
+
+	if (*ptr == val)
+		return;
+
+	if (obj->document() != null and (idx = obj->lineNumber()) != -1)
+	{
+		QString before = obj->asText();
+		*ptr = val;
+		QString after = obj->asText();
+
+		if (before != after)
+		{
+			obj->document().toStrongRef()->addToHistory (new EditHistory (idx, before, after));
+			g_win->R()->compileObject (obj);
+			CurrentDocument()->redoVertices();
+		}
+	}
+	else
+		*ptr = val;
+}
+
+// =============================================================================
+//
+void LDObject::setColor (LDColor const& val)
+{
+	changeProperty (self(), &m_color, val);
+}
+
+// =============================================================================
+//
+const Vertex& LDObject::vertex (int i) const
+{
+	return m_coords[i];
+}
+
+// =============================================================================
+//
+void LDObject::setVertex (int i, const Vertex& vert)
+{
+	changeProperty (self(), &m_coords[i], vert);
+}
+
+// =============================================================================
+//
+void LDMatrixObject::setPosition (const Vertex& a)
+{
+	changeProperty (self(), &m_position, a);
+}
+
+// =============================================================================
+//
+void LDMatrixObject::setTransform (const Matrix& val)
+{
+	changeProperty (self(), &m_transform, val);
+}
+
+// =============================================================================
+//
+void LDObject::select()
+{
+	assert (document() != null);
+	document().toStrongRef()->addToSelection (self());
+
+	// If this object is inverted with INVERTNEXT, pick the INVERTNEXT as well.
+	/*
+	LDBFCPtr invertnext;
+
+	if (previousIsInvertnext (invertnext))
+		invertnext->select();
+	*/
+}
+
+// =============================================================================
+//
+void LDObject::deselect()
+{
+	assert (document() != null);
+	document().toStrongRef()->removeFromSelection (self());
+
+	// If this object is inverted with INVERTNEXT, deselect the INVERTNEXT as well.
+	LDBFCPtr invertnext;
+
+	if (previousIsInvertnext (invertnext))
+		invertnext->deselect();
+}
+
+// =============================================================================
+//
+QString PreferredLicenseText()
+{
+	return (cfg::UseCALicense ? CALicenseText : "");
+}
+
+// =============================================================================
+//
+LDObjectPtr LDObject::createCopy() const
+{
+	LDObjectPtr copy = ParseLine (asText());
+	return copy;
+}
+
+// =============================================================================
+//
+void LDSubfile::setFileInfo (const LDDocumentPtr& a)
+{
+	changeProperty (self(), &m_fileInfo, a);
+
+	// If it's an immediate subfile reference (i.e. this subfile belongs in an
+	// explicit file), we need to pre-compile the GL polygons for the document
+	// if they don't exist already.
+	if (a != null and
+		a->isImplicit() == false and
+		a->polygonData().isEmpty())
+	{
+		a->initializeCachedData();
+	}
+};
+
+void LDObject::getVertices (QVector<Vertex>& verts) const
+{
+	for (int i = 0; i < numVertices(); ++i)
+		verts << vertex (i);
+}
+
+void LDSubfile::getVertices (QVector<Vertex>& verts) const
+{
+	verts << fileInfo()->inlineVertices();
+}
--- a/src/main.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QApplication>
-#include <QMessageBox>
-#include <QAbstractButton>
-#include <QFile>
-#include <QTextStream>
-#include <QDir>
-#include "mainWindow.h"
-#include "ldDocument.h"
-#include "miscallenous.h"
-#include "configuration.h"
-#include "colors.h"
-#include "basics.h"
-#include "primitives.h"
-#include "glRenderer.h"
-#include "configDialog.h"
-#include "dialogs.h"
-#include "crashCatcher.h"
-
-MainWindow* g_win = null;
-static QString g_versionString, g_fullVersionString;
-static bool g_IsExiting (false);
-
-const Vertex Origin (0.0f, 0.0f, 0.0f);
-const Matrix IdentityMatrix ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
-
-CFGENTRY (Bool, FirstStart, true)
-
-// =============================================================================
-//
-int main (int argc, char* argv[])
-{
-	QApplication app (argc, argv);
-	app.setOrganizationName (APPNAME);
-	app.setApplicationName (APPNAME);
-	InitCrashCatcher();
-	Config::Initialize();
-
-	// Load or create the configuration
-	if (not Config::Load())
-	{
-		print ("Creating configuration file...\n");
-
-		if (Config::Save())
-			print ("Configuration file successfully created.\n");
-		else
-			Critical ("Failed to create configuration file!\n");
-	}
-
-	LDPaths::initPaths();
-	InitColors();
-	LoadPrimitives();
-	MainWindow* win = new MainWindow;
-	newFile();
-	win->show();
-
-	// If this is the first start, get the user to configuration. Especially point
-	// them to the profile tab, it's the most important form to fill in.
-	if (cfg::FirstStart)
-	{
-		(new ConfigDialog (ConfigDialog::ProfileTab))->exec();
-		cfg::FirstStart = false;
-		Config::Save();
-	}
-
-	// Process the command line
-	for (int arg = 1; arg < argc; ++arg)
-		OpenMainModel (QString::fromLocal8Bit (argv[arg]));
-
-	int result = app.exec();
-	g_IsExiting = true;
-	return result;
-}
-
-bool IsExiting()
-{
-	return g_IsExiting;
-}
-
-void Exit()
-{
-	g_IsExiting = true;
-	exit (EXIT_SUCCESS);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,101 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QApplication>
+#include <QMessageBox>
+#include <QAbstractButton>
+#include <QFile>
+#include <QTextStream>
+#include <QDir>
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "configuration.h"
+#include "colors.h"
+#include "basics.h"
+#include "primitives.h"
+#include "glRenderer.h"
+#include "configDialog.h"
+#include "dialogs.h"
+#include "crashCatcher.h"
+
+MainWindow* g_win = null;
+static QString g_versionString, g_fullVersionString;
+static bool g_IsExiting (false);
+
+const Vertex Origin (0.0f, 0.0f, 0.0f);
+const Matrix IdentityMatrix ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
+
+CFGENTRY (Bool, FirstStart, true)
+
+// =============================================================================
+//
+int main (int argc, char* argv[])
+{
+	QApplication app (argc, argv);
+	app.setOrganizationName (APPNAME);
+	app.setApplicationName (APPNAME);
+	InitCrashCatcher();
+	Config::Initialize();
+
+	// Load or create the configuration
+	if (not Config::Load())
+	{
+		print ("Creating configuration file...\n");
+
+		if (Config::Save())
+			print ("Configuration file successfully created.\n");
+		else
+			Critical ("Failed to create configuration file!\n");
+	}
+
+	LDPaths::initPaths();
+	InitColors();
+	LoadPrimitives();
+	MainWindow* win = new MainWindow;
+	newFile();
+	win->show();
+
+	// If this is the first start, get the user to configuration. Especially point
+	// them to the profile tab, it's the most important form to fill in.
+	if (cfg::FirstStart)
+	{
+		(new ConfigDialog (ConfigDialog::ProfileTab))->exec();
+		cfg::FirstStart = false;
+		Config::Save();
+	}
+
+	// Process the command line
+	for (int arg = 1; arg < argc; ++arg)
+		OpenMainModel (QString::fromLocal8Bit (argv[arg]));
+
+	int result = app.exec();
+	g_IsExiting = true;
+	return result;
+}
+
+bool IsExiting()
+{
+	return g_IsExiting;
+}
+
+void Exit()
+{
+	g_IsExiting = true;
+	exit (EXIT_SUCCESS);
+}
--- a/src/mainWindow.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1191 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QGridLayout>
-#include <QMessageBox>
-#include <QEvent>
-#include <QContextMenuEvent>
-#include <QMenuBar>
-#include <QStatusBar>
-#include <QSplitter>
-#include <QListWidget>
-#include <QToolButton>
-#include <QComboBox>
-#include <QDialogButtonBox>
-#include <QToolBar>
-#include <QProgressBar>
-#include <QLabel>
-#include <QFileDialog>
-#include <QPushButton>
-#include <QCoreApplication>
-#include <QTimer>
-#include <QMetaMethod>
-#include <QSettings>
-#include "main.h"
-#include "glRenderer.h"
-#include "mainWindow.h"
-#include "ldDocument.h"
-#include "configuration.h"
-#include "miscallenous.h"
-#include "colors.h"
-#include "editHistory.h"
-#include "radioGroup.h"
-#include "addObjectDialog.h"
-#include "messageLog.h"
-#include "configuration.h"
-#include "ui_ldforge.h"
-#include "primitives.h"
-#include "editmodes/abstractEditMode.h"
-
-static bool g_isSelectionLocked = false;
-static QMap<QAction*, QKeySequence> g_defaultShortcuts;
-
-CFGENTRY (Bool, ColorizeObjectsList, true)
-CFGENTRY (String, QuickColorToolbar, "4:25:14:27:2:3:11:1:22:|:0:72:71:15")
-CFGENTRY (Bool, ListImplicitFiles, false)
-CFGENTRY (List, HiddenToolbars, {})
-EXTERN_CFGENTRY (List, RecentFiles)
-EXTERN_CFGENTRY (Bool, DrawAxes)
-EXTERN_CFGENTRY (String, MainColor)
-EXTERN_CFGENTRY (Float, MainColorAlpha)
-EXTERN_CFGENTRY (Bool, DrawWireframe)
-EXTERN_CFGENTRY (Bool, BFCRedGreenView)
-EXTERN_CFGENTRY (Bool, DrawAngles)
-EXTERN_CFGENTRY (Bool, RandomColors)
-EXTERN_CFGENTRY (Bool, DrawSurfaces)
-EXTERN_CFGENTRY (Bool, DrawEdgeLines)
-EXTERN_CFGENTRY (Bool, DrawConditionalLines)
-
-// =============================================================================
-//
-MainWindow::MainWindow (QWidget* parent, Qt::WindowFlags flags) :
-	QMainWindow (parent, flags)
-{
-	g_win = this;
-	ui = new Ui_LDForgeUI;
-	ui->setupUi (this);
-	m_isUpdatingTabs = false;
-	m_renderer = new GLRenderer (this);
-	m_tabBar = new QTabBar;
-	m_tabBar->setTabsClosable (true);
-	ui->verticalLayout->insertWidget (0, m_tabBar);
-
-	// Stuff the renderer into its frame
-	QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame);
-	rendererLayout->addWidget (R());
-
-	connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged()));
-	connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*)));
-	connect (m_tabBar, SIGNAL (currentChanged(int)), this, SLOT (changeCurrentFile()));
-	connect (m_tabBar, SIGNAL (tabCloseRequested (int)), this, SLOT (closeTab (int)));
-
-	if (ActivePrimitiveScanner() != null)
-		connect (ActivePrimitiveScanner(), SIGNAL (workDone()), this, SLOT (updatePrimitives()));
-	else
-		updatePrimitives();
-
-	m_messageLog = new MessageManager;
-	m_messageLog->setRenderer (R());
-	m_renderer->setMessageLog (m_messageLog);
-	m_quickColors = LoadQuickColorList();
-	slot_selectionChanged();
-	setStatusBar (new QStatusBar);
-	updateActions();
-
-	// Connect all actions and save default sequences
-	applyToActions ([&](QAction* act)
-	{
-		connect (act, SIGNAL (triggered()), this, SLOT (actionTriggered()));
-		g_defaultShortcuts[act] = act->shortcut();
-	});
-
-	updateGridToolBar();
-	updateEditModeActions();
-	updateRecentFilesMenu();
-	updateColorToolbar();
-	updateTitle();
-	loadShortcuts (Config::SettingsObject());
-	setMinimumSize (300, 200);
-	connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup()));
-	connect (ui->ringToolHiRes, SIGNAL (clicked (bool)), this, SLOT (ringToolHiResClicked (bool)));
-	connect (ui->ringToolSegments, SIGNAL (valueChanged (int)),
-		this, SLOT (circleToolSegmentsChanged()));
-	circleToolSegmentsChanged(); // invoke it manually for initial label text
-
-	for (QVariant const& toolbarname : cfg::HiddenToolbars)
-	{
-		QToolBar* toolbar = findChild<QToolBar*> (toolbarname.toString());
-
-		if (toolbar != null)
-			toolbar->hide();
-	}
-}
-
-MainWindow::~MainWindow()
-{
-	g_win = null;
-}
-
-// =============================================================================
-//
-void MainWindow::actionTriggered()
-{
-	// Get the name of the sender object and use it to compose the slot name,
-	// then invoke this slot to call the action.
-	QMetaObject::invokeMethod (this,
-		qPrintable (format ("slot_%1", sender()->objectName())), Qt::DirectConnection);
-	endAction();
-}
-
-// =============================================================================
-//
-void MainWindow::endAction()
-{
-	// Add a step in the history now.
-	CurrentDocument()->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 (CurrentDocument());
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_lastSecondCleanup()
-{
-	delete m_renderer;
-	delete ui;
-}
-
-// =============================================================================
-//
-void MainWindow::updateRecentFilesMenu()
-{
-	// First, clear any items in the recent files menu
-for (QAction * recent : m_recentFiles)
-		delete recent;
-
-	m_recentFiles.clear();
-
-	QAction* first = null;
-
-	for (const QVariant& it : cfg::RecentFiles)
-	{
-		QString file = it.toString();
-		QAction* recent = new QAction (GetIcon ("open-recent"), file, this);
-
-		connect (recent, SIGNAL (triggered()), this, SLOT (slot_recentFile()));
-		ui->menuOpenRecent->insertAction (first, recent);
-		m_recentFiles << recent;
-		first = recent;
-	}
-}
-
-// =============================================================================
-//
-QList<LDQuickColor> LoadQuickColorList()
-{
-	QList<LDQuickColor> colors;
-
-	for (QString colorname : cfg::QuickColorToolbar.split (":"))
-	{
-		if (colorname == "|")
-			colors << LDQuickColor::getSeparator();
-		else
-		{
-			LDColor col = LDColor::fromIndex (colorname.toLong());
-
-			if (col != null)
-				colors << LDQuickColor (col, null);
-		}
-	}
-
-	return colors;
-}
-
-// =============================================================================
-//
-void MainWindow::updateColorToolbar()
-{
-	m_colorButtons.clear();
-	ui->toolBarColors->clear();
-	ui->toolBarColors->addAction (ui->actionUncolor);
-	ui->toolBarColors->addSeparator();
-
-	for (LDQuickColor& entry : m_quickColors)
-	{
-		if (entry.isSeparator())
-		{
-			ui->toolBarColors->addSeparator();
-		}
-		else
-		{
-			QToolButton* colorButton = new QToolButton;
-			colorButton->setIcon (MakeColorIcon (entry.color(), 16));
-			colorButton->setIconSize (QSize (16, 16));
-			colorButton->setToolTip (entry.color().name());
-
-			connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor()));
-			ui->toolBarColors->addWidget (colorButton);
-			m_colorButtons << colorButton;
-
-			entry.setToolButton (colorButton);
-		}
-	}
-
-	updateGridToolBar();
-}
-
-// =============================================================================
-//
-void MainWindow::updateGridToolBar()
-{
-	// Ensure that the current grid - and only the current grid - is selected.
-	ui->actionGridCoarse->setChecked (cfg::Grid == Grid::Coarse);
-	ui->actionGridMedium->setChecked (cfg::Grid == Grid::Medium);
-	ui->actionGridFine->setChecked (cfg::Grid == Grid::Fine);
-}
-
-// =============================================================================
-//
-void MainWindow::updateTitle()
-{
-	QString title = format (APPNAME " %1", VersionString());
-
-	// Append our current file if we have one
-	if (CurrentDocument())
-	{
-		title += ": ";
-		title += CurrentDocument()->getDisplayName();
-
-		if (CurrentDocument()->getObjectCount() > 0 and
-			CurrentDocument()->getObject (0)->type() == OBJ_Comment)
-		{
-			// Append title
-			LDCommentPtr comm = CurrentDocument()->getObject (0).staticCast<LDComment>();
-			title += format (": %1", comm->text());
-		}
-
-		if (CurrentDocument()->hasUnsavedChanges())
-			title += '*';
-	}
-
-#ifdef DEBUG
-	title += " [debug build]";
-#elif BUILD_ID != BUILD_RELEASE
-	title += " [pre-release build]";
-#endif // DEBUG
-
-	if (CommitTimeString()[0] != '\0')
-		title += format (" (%1)", QString::fromUtf8 (CommitTimeString()));
-
-	setWindowTitle (title);
-}
-
-// =============================================================================
-//
-int MainWindow::deleteSelection()
-{
-	if (Selection().isEmpty())
-		return 0;
-
-	LDObjectList selCopy = Selection();
-
-	// Delete the objects that were being selected
-	for (LDObjectPtr obj : selCopy)
-		obj->destroy();
-
-	refresh();
-	return selCopy.size();
-}
-
-// =============================================================================
-//
-void MainWindow::buildObjList()
-{
-	if (not CurrentDocument())
-		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 (LDObjectPtr obj : CurrentDocument()->objects())
-	{
-		QString descr;
-
-		switch (obj->type())
-		{
-			case OBJ_Comment:
-			{
-				descr = obj.staticCast<LDComment>()->text();
-
-				// Remove leading whitespace
-				while (descr[0] == ' ')
-					descr.remove (0, 1);
-
-				break;
-			}
-
-			case OBJ_Empty:
-				break; // leave it empty
-
-			case OBJ_Line:
-			case OBJ_Triangle:
-			case OBJ_Quad:
-			case OBJ_CondLine:
-			{
-				for (int i = 0; i < obj->numVertices(); ++i)
-				{
-					if (i != 0)
-						descr += ", ";
-
-					descr += obj->vertex (i).toString (true);
-				}
-				break;
-			}
-
-			case OBJ_Error:
-			{
-				descr = format ("ERROR: %1", obj->asText());
-				break;
-			}
-
-			case OBJ_Subfile:
-			{
-				LDSubfilePtr ref = obj.staticCast<LDSubfile>();
-
-				descr = format ("%1 %2, (", ref->fileInfo()->getDisplayName(), ref->position().toString (true));
-
-				for (int i = 0; i < 9; ++i)
-					descr += format ("%1%2", ref->transform()[i], (i != 8) ? " " : "");
-
-				descr += ')';
-				break;
-			}
-
-			case OBJ_BFC:
-			{
-				descr = LDBFC::StatementStrings[int (obj.staticCast<LDBFC>()->statement())];
-				break;
-			}
-
-			case OBJ_Overlay:
-			{
-				LDOverlayPtr ovl = obj.staticCast<LDOverlay>();
-				descr = format ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->camera()],
-					Basename (ovl->fileName()), ovl->x(), ovl->y(),
-					ovl->width(), ovl->height());
-				break;
-			}
-
-			default:
-			{
-				descr = obj->typeName();
-				break;
-			}
-		}
-
-		QListWidgetItem* item = new QListWidgetItem (descr);
-		item->setIcon (GetIcon (obj->typeName()));
-
-		// Use italic font if hidden
-		if (obj->isHidden())
-		{
-			QFont font = item->font();
-			font.setItalic (true);
-			item->setFont (font);
-		}
-
-		// Color gibberish orange on red so it stands out.
-		if (obj->type() == OBJ_Error)
-		{
-			item->setBackground (QColor ("#AA0000"));
-			item->setForeground (QColor ("#FFAA00"));
-		}
-		elif (cfg::ColorizeObjectsList and obj->isColored() and
-			obj->color() != null and obj->color() != MainColor() and obj->color() != EdgeColor())
-		{
-			// If the object isn't in the main or edge color, draw this
-			// list entry in said color.
-			item->setForeground (obj->color().faceColor());
-		}
-
-		obj->qObjListEntry = item;
-		ui->objectList->insertItem (ui->objectList->count(), item);
-	}
-
-	g_isSelectionLocked = false;
-	updateSelection();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-void MainWindow::scrollToSelection()
-{
-	if (Selection().isEmpty())
-		return;
-
-	LDObjectPtr obj = Selection().last();
-	ui->objectList->scrollToItem (obj->qObjListEntry);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_selectionChanged()
-{
-	if (g_isSelectionLocked == true or CurrentDocument() == null)
-		return;
-
-	LDObjectList priorSelection = Selection();
-
-	// Get the objects from the object list selection
-	CurrentDocument()->clearSelection();
-	const QList<QListWidgetItem*> items = ui->objectList->selectedItems();
-
-	for (LDObjectPtr obj : CurrentDocument()->objects())
-	{
-		for (QListWidgetItem* item : items)
-		{
-			if (item == obj->qObjListEntry)
-			{
-				obj->select();
-				break;
-			}
-		}
-	}
-
-	// The select() method calls may have selected additional items (i.e. invertnexts)
-	// Update it all now.
-	updateSelection();
-
-	// Update the GL renderer
-	LDObjectList compound = priorSelection + Selection();
-	RemoveDuplicates (compound);
-
-	for (LDObjectPtr obj : compound)
-		R()->compileObject (obj);
-
-	R()->update();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_recentFile()
-{
-	QAction* qAct = static_cast<QAction*> (sender());
-	OpenMainModel (qAct->text());
-}
-
-// =============================================================================
-//
-void MainWindow::slot_quickColor()
-{
-	QToolButton* button = static_cast<QToolButton*> (sender());
-	LDColor col = null;
-
-	for (const LDQuickColor& entry : m_quickColors)
-	{
-		if (entry.toolButton() == button)
-		{
-			col = entry.color();
-			break;
-		}
-	}
-
-	if (col == null)
-		return;
-
-	for (LDObjectPtr obj : Selection())
-	{
-		if (not obj->isColored())
-			continue; // uncolored object
-
-		obj->setColor (col);
-		R()->compileObject (obj);
-	}
-
-	endAction();
-	refresh();
-}
-
-// =============================================================================
-//
-int MainWindow::getInsertionPoint()
-{
-	// If we have a selection, put the item after it.
-	if (not Selection().isEmpty())
-		return Selection().last()->lineNumber() + 1;
-
-	// Otherwise place the object at the end.
-	return CurrentDocument()->getObjectCount();
-}
-
-// =============================================================================
-//
-void MainWindow::doFullRefresh()
-{
-	buildObjList();
-	m_renderer->hardRefresh();
-}
-
-// =============================================================================
-//
-void MainWindow::refresh()
-{
-	buildObjList();
-	m_renderer->update();
-}
-
-// =============================================================================
-//
-void MainWindow::updateSelection()
-{
-	g_isSelectionLocked = true;
-	QItemSelection itemselect;
-	int top = -1;
-	int bottom = -1;
-
-	for (LDObjectPtr obj : Selection())
-	{
-		if (obj->qObjListEntry == null)
-			continue;
-
-		int row = ui->objectList->row (obj->qObjListEntry);
-
-		if (top == -1)
-		{
-			top = bottom = row;
-		}
-		else
-		{
-			if (row != bottom + 1)
-			{
-				itemselect.select (ui->objectList->model()->index (top, 0),
-					ui->objectList->model()->index (bottom, 0));
-				top = -1;
-			}
-
-			bottom = row;
-		}
-	}
-
-	if (top != -1)
-	{
-		itemselect.select (ui->objectList->model()->index (top, 0),
-			ui->objectList->model()->index (bottom, 0));
-	}
-
-	ui->objectList->selectionModel()->select (itemselect, QItemSelectionModel::ClearAndSelect);
-	g_isSelectionLocked = false;
-}
-
-// =============================================================================
-//
-LDColor MainWindow::getSelectedColor()
-{
-	LDColor result;
-
-	for (LDObjectPtr obj : Selection())
-	{
-		if (not obj->isColored())
-			continue; // doesn't use color
-
-		if (result != null and obj->color() != result)
-			return null; // No consensus in object color
-
-		if (result == null)
-			result = obj->color();
-	}
-
-	return result;
-}
-
-// =============================================================================
-//
-void MainWindow::closeEvent (QCloseEvent* ev)
-{
-	// Check whether it's safe to close all files.
-	if (not IsSafeToCloseAll())
-	{
-		ev->ignore();
-		return;
-	}
-
-	// Save the toolbar set
-	cfg::HiddenToolbars.clear();
-
-	for (QToolBar* toolbar : findChildren<QToolBar*>())
-	{
-		if (toolbar->isHidden())
-			cfg::HiddenToolbars << toolbar->objectName();
-	}
-
-	// Save the configuration before leaving.
-	Config::Save();
-	ev->accept();
-}
-
-// =============================================================================
-//
-void MainWindow::spawnContextMenu (const QPoint pos)
-{
-	const bool single = (Selection().size() == 1);
-	LDObjectPtr singleObj = single ? Selection().first() : LDObjectPtr();
-
-	bool hasSubfiles = false;
-
-	for (LDObjectPtr obj : Selection())
-	{
-		if (obj->type() == OBJ_Subfile)
-		{
-			hasSubfiles = true;
-			break;
-		}
-	}
-
-	QMenu* contextMenu = new QMenu;
-
-	if (single and singleObj->type() != OBJ_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);
-
-	if (hasSubfiles)
-	{
-		contextMenu->addSeparator();
-		contextMenu->addAction (ui->actionOpenSubfiles);
-	}
-
-	contextMenu->addSeparator();
-	contextMenu->addAction (ui->actionModeSelect);
-	contextMenu->addAction (ui->actionModeDraw);
-	contextMenu->addAction (ui->actionModeCircle);
-
-	if (not Selection().isEmpty())
-	{
-		contextMenu->addSeparator();
-		contextMenu->addAction (ui->actionSubfileSelection);
-	}
-
-	if (R()->camera() != EFreeCamera)
-	{
-		contextMenu->addSeparator();
-		contextMenu->addAction (ui->actionSetDrawDepth);
-	}
-
-	contextMenu->exec (pos);
-}
-
-// =============================================================================
-//
-void MainWindow::deleteByColor (LDColor color)
-{
-	LDObjectList objs;
-
-	for (LDObjectPtr obj : CurrentDocument()->objects())
-	{
-		if (not obj->isColored() or obj->color() != color)
-			continue;
-
-		objs << obj;
-	}
-
-	for (LDObjectPtr obj : objs)
-		obj->destroy();
-}
-
-// =============================================================================
-//
-void MainWindow::updateEditModeActions()
-{
-	const EditModeType mode = R()->currentEditModeType();
-	ui->actionModeSelect->setChecked (mode == EditModeType::Select);
-	ui->actionModeDraw->setChecked (mode == EditModeType::Draw);
-	ui->actionModeRectangle->setChecked (mode == EditModeType::Rectangle);
-	ui->actionModeCircle->setChecked (mode == EditModeType::Circle);
-	ui->actionModeMagicWand->setChecked (mode == EditModeType::MagicWand);
-	ui->actionModeLinePath->setChecked (mode == EditModeType::LinePath);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_editObject (QListWidgetItem* listitem)
-{
-	for (LDObjectPtr it : CurrentDocument()->objects())
-	{
-		if (it->qObjListEntry == listitem)
-		{
-			AddObjectDialog::staticDialog (it->type(), it);
-			break;
-		}
-	}
-}
-
-// =============================================================================
-//
-bool MainWindow::save (LDDocumentPtr doc, bool saveAs)
-{
-	QString path = doc->fullPath();
-	int64 savesize;
-
-	if (saveAs or path.isEmpty())
-	{
-		QString name = doc->defaultName();
-
-		if (not doc->fullPath().isEmpty()) 
-			name = doc->fullPath();
-		elif (not doc->name().isEmpty())
-			name = doc->name();
-
-		name.replace ("\\", "/");
-		path = QFileDialog::getSaveFileName (g_win, tr ("Save As"),
-			name, tr ("LDraw files (*.dat *.ldr)"));
-
-		if (path.isEmpty())
-		{
-			// User didn't give a file name, abort.
-			return false;
-		}
-	}
-
-	if (doc->save (path, &savesize))
-	{
-		if (doc == CurrentDocument())
-			updateTitle();
-
-		print ("Saved to %1 (%2)", path, MakePrettyFileSize (savesize));
-
-		// Add it to recent files
-		AddRecentFile (path);
-		return true;
-	}
-
-	QString message = format (tr ("Failed to save to %1: %2"), path, strerror (errno));
-
-	// Tell the user the save failed, and give the option for saving as with it.
-	QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win);
-
-	// Add a save-as button
-	QPushButton* saveAsBtn = new QPushButton (tr ("Save As"));
-	saveAsBtn->setIcon (GetIcon ("file-save-as"));
-	dlg.addButton (saveAsBtn, QMessageBox::ActionRole);
-	dlg.setDefaultButton (QMessageBox::Close);
-	dlg.exec();
-
-	if (dlg.clickedButton() == saveAsBtn)
-		return save (doc, true); // yay recursion!
-
-	return false;
-}
-
-void MainWindow::addMessage (QString msg)
-{
-	m_messageLog->addLine (msg);
-}
-
-// ============================================================================
-void ObjectList::contextMenuEvent (QContextMenuEvent* ev)
-{
-	g_win->spawnContextMenu (ev->globalPos());
-}
-
-// =============================================================================
-//
-QPixmap GetIcon (QString iconName)
-{
-	return (QPixmap (format (":/icons/%1.png", iconName)));
-}
-
-// =============================================================================
-//
-bool Confirm (const QString& message)
-{
-	return Confirm (MainWindow::tr ("Confirm"), message);
-}
-
-// =============================================================================
-//
-bool Confirm (const QString& title, const QString& message)
-{
-	return QMessageBox::question (g_win, title, message,
-		(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes;
-}
-
-// =============================================================================
-//
-void Critical (const QString& message)
-{
-	QMessageBox::critical (g_win, MainWindow::tr ("Error"), message,
-		(QMessageBox::Close), QMessageBox::Close);
-}
-
-// =============================================================================
-//
-QIcon MakeColorIcon (LDColor colinfo, const int size)
-{
-	// Create an image object and link a painter to it.
-	QImage img (size, size, QImage::Format_ARGB32);
-	QPainter paint (&img);
-	QColor col = colinfo.faceColor();
-
-	if (colinfo == MainColor())
-	{
-		// Use the user preferences for main color here
-		col = cfg::MainColor;
-		col.setAlphaF (cfg::MainColorAlpha);
-	}
-
-	// 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<LDColor, int> counts;
-
-	for (LDObjectPtr obj : CurrentDocument()->objects())
-	{
-		if (not obj->isColored() or obj->color() == null)
-			continue;
-
-		if (counts.find (obj->color()) == counts.end())
-			counts[obj->color()] = 1;
-		else
-			counts[obj->color()]++;
-	}
-
-	box->clear();
-	int row = 0;
-
-	for (const auto& pair : counts)
-	{
-		QIcon ico = MakeColorIcon (pair.first, 16);
-		box->addItem (ico, format ("[%1] %2 (%3 object%4)",
-			pair.first, pair.first.name(), pair.second, Plural (pair.second)));
-		box->setItemData (row, pair.first.index());
-
-		++row;
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::updateDocumentList()
-{
-	m_isUpdatingTabs = true;
-
-	while (m_tabBar->count() > 0)
-		m_tabBar->removeTab (0);
-
-	for (LDDocumentPtr f : LDDocument::explicitDocuments())
-	{
-		// Add an item to the list for this file and store the tab index
-		// in the document so we can find documents by tab index.
-		f->setTabIndex (m_tabBar->addTab (""));
-		updateDocumentListItem (f);
-	}
-
-	m_isUpdatingTabs = false;
-}
-
-// =============================================================================
-//
-void MainWindow::updateDocumentListItem (LDDocumentPtr doc)
-{
-	bool oldUpdatingTabs = m_isUpdatingTabs;
-	m_isUpdatingTabs = true;
-
-	if (doc->tabIndex() == -1)
-	{
-		// We don't have a list item for this file, so the list either doesn't
-		// exist yet or is out of date. Build the list now.
-		updateDocumentList();
-		return;
-	}
-
-	// If this is the current file, it also needs to be the selected item on
-	// the list.
-	if (doc == CurrentDocument())
-		m_tabBar->setCurrentIndex (doc->tabIndex());
-
-	m_tabBar->setTabText (doc->tabIndex(), doc->getDisplayName());
-
-	// If the document.has unsaved changes, draw a little icon next to it to mark that.
-	m_tabBar->setTabIcon (doc->tabIndex(), doc->hasUnsavedChanges() ? GetIcon ("file-save") : QIcon());
-	m_tabBar->setTabData (doc->tabIndex(), doc->name());
-	m_isUpdatingTabs = oldUpdatingTabs;
-}
-
-// =============================================================================
-//
-// A file is selected from the list of files on the left of the screen. Find out
-// which file was picked and change to it.
-//
-void MainWindow::changeCurrentFile()
-{
-	if (m_isUpdatingTabs)
-		return;
-
-	LDDocumentPtr f;
-	int tabIndex = m_tabBar->currentIndex();
-
-	// Find the file pointer of the item that was selected.
-	for (LDDocumentPtr it : LDDocument::explicitDocuments())
-	{
-		if (it->tabIndex() == tabIndex)
-		{
-			f = it;
-			break;
-		}
-	}
-
-	// If we picked the same file we're currently on, we don't need to do
-	// anything.
-	if (f == null or f == CurrentDocument())
-		return;
-
-	LDDocument::setCurrent (f);
-}
-
-// =============================================================================
-//
-void MainWindow::refreshObjectList()
-{
-#if 0
-	ui->objectList->clear();
-	LDDocumentPtr f = getCurrentDocument();
-
-for (LDObjectPtr obj : *f)
-		ui->objectList->addItem (obj->qObjListEntry);
-
-#endif
-
-	buildObjList();
-}
-
-// =============================================================================
-//
-void MainWindow::updateActions()
-{
-	if (CurrentDocument() != null and CurrentDocument()->history() != null)
-	{
-		History* his = CurrentDocument()->history();
-		int pos = his->position();
-		ui->actionUndo->setEnabled (pos != -1);
-		ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1);
-	}
-
-	ui->actionWireframe->setChecked (cfg::DrawWireframe);
-	ui->actionAxes->setChecked (cfg::DrawAxes);
-	ui->actionBFCView->setChecked (cfg::BFCRedGreenView);
-	ui->actionRandomColors->setChecked (cfg::RandomColors);
-	ui->actionDrawAngles->setChecked (cfg::DrawAngles);
-	ui->actionDrawSurfaces->setChecked (cfg::DrawSurfaces);
-	ui->actionDrawEdgeLines->setChecked (cfg::DrawEdgeLines);
-	ui->actionDrawConditionalLines->setChecked (cfg::DrawConditionalLines);
-}
-
-// =============================================================================
-//
-void MainWindow::updatePrimitives()
-{
-	PopulatePrimitives (ui->primitives);
-}
-
-// =============================================================================
-//
-void MainWindow::closeTab (int tabindex)
-{
-	LDDocumentPtr doc = FindDocument (m_tabBar->tabData (tabindex).toString());
-
-	if (doc == null)
-		return;
-
-	doc->dismiss();
-}
-
-// =============================================================================
-//
-void MainWindow::loadShortcuts (QSettings const* settings)
-{
-	for (QAction* act : findChildren<QAction*>())
-	{
-		QKeySequence seq = settings->value ("shortcut_" + act->objectName(), act->shortcut()).value<QKeySequence>();
-		act->setShortcut (seq);
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::saveShortcuts (QSettings* settings)
-{
-	applyToActions ([&](QAction* act)
-	{
-		QString const key = "shortcut_" + act->objectName();
-
-		if (g_defaultShortcuts[act] != act->shortcut())
-			settings->setValue (key, act->shortcut());
-		else
-			settings->remove (key);
-	});
-}
-
-// =============================================================================
-//
-void MainWindow::applyToActions (std::function<void(QAction*)> function)
-{
-	for (QAction* act : findChildren<QAction*>())
-	{
-		if (not act->objectName().isEmpty())
-			function (act);
-	}
-}
-
-// =============================================================================
-//
-QKeySequence MainWindow::defaultShortcut (QAction* act) // [static]
-{
-	return g_defaultShortcuts[act];
-}
-
-// =============================================================================
-//
-bool MainWindow::ringToolHiRes() const
-{
-	return ui->ringToolHiRes->isChecked();
-}
-
-// =============================================================================
-//
-int MainWindow::ringToolSegments() const
-{
-	return ui->ringToolSegments->value();
-}
-
-// =============================================================================
-//
-void MainWindow::ringToolHiResClicked (bool checked)
-{
-	if (checked)
-	{
-		ui->ringToolSegments->setMaximum (HighResolution);
-		ui->ringToolSegments->setValue (ui->ringToolSegments->value() * 3);
-	}
-	else
-	{
-		ui->ringToolSegments->setValue (ui->ringToolSegments->value() / 3);
-		ui->ringToolSegments->setMaximum (LowResolution);
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::circleToolSegmentsChanged()
-{
-	int numerator (ui->ringToolSegments->value());
-	int denominator (ui->ringToolHiRes->isChecked() ? HighResolution : LowResolution);
-	Simplify (numerator, denominator);
-	ui->ringToolSegmentsLabel->setText (format ("%1 / %2", numerator, denominator));
-}
-
-// =============================================================================
-//
-QImage GetImageFromScreencap (uchar* data, int w, int h)
-{
-	// GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well.
-	return QImage (data, w, h, QImage::Format_ARGB32).rgbSwapped().mirrored();
-}
-
-// =============================================================================
-//
-LDQuickColor::LDQuickColor (LDColor color, QToolButton* toolButton) :
-	m_color (color),
-	m_toolButton (toolButton) {}
-
-// =============================================================================
-//
-LDQuickColor LDQuickColor::getSeparator()
-{
-	return LDQuickColor (null, null);
-}
-
-// =============================================================================
-//
-bool LDQuickColor::isSeparator() const
-{
-	return color() == null;
-}
-
-void PopulatePrimitives (QTreeWidget* tw, QString const& selectByDefault)
-{
-	tw->clear();
-
-	for (PrimitiveCategory* cat : g_PrimitiveCategories)
-	{
-		SubfileListItem* parentItem = new SubfileListItem (tw, null);
-		parentItem->setText (0, cat->name());
-		QList<QTreeWidgetItem*> subfileItems;
-
-		for (Primitive& prim : cat->prims)
-		{
-			SubfileListItem* item = new SubfileListItem (parentItem, &prim);
-			item->setText (0, format ("%1 - %2", prim.name, prim.title));
-			subfileItems << item;
-
-			// If this primitive is the one the current object points to,
-			// select it by default
-			if (selectByDefault == prim.name)
-				tw->setCurrentItem (item);
-		}
-
-		tw->addTopLevelItem (parentItem);
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mainWindow.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,1191 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QGridLayout>
+#include <QMessageBox>
+#include <QEvent>
+#include <QContextMenuEvent>
+#include <QMenuBar>
+#include <QStatusBar>
+#include <QSplitter>
+#include <QListWidget>
+#include <QToolButton>
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QToolBar>
+#include <QProgressBar>
+#include <QLabel>
+#include <QFileDialog>
+#include <QPushButton>
+#include <QCoreApplication>
+#include <QTimer>
+#include <QMetaMethod>
+#include <QSettings>
+#include "main.h"
+#include "glRenderer.h"
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "configuration.h"
+#include "miscallenous.h"
+#include "colors.h"
+#include "editHistory.h"
+#include "radioGroup.h"
+#include "addObjectDialog.h"
+#include "messageLog.h"
+#include "configuration.h"
+#include "ui_ldforge.h"
+#include "primitives.h"
+#include "editmodes/abstractEditMode.h"
+
+static bool g_isSelectionLocked = false;
+static QMap<QAction*, QKeySequence> g_defaultShortcuts;
+
+CFGENTRY (Bool, ColorizeObjectsList, true)
+CFGENTRY (String, QuickColorToolbar, "4:25:14:27:2:3:11:1:22:|:0:72:71:15")
+CFGENTRY (Bool, ListImplicitFiles, false)
+CFGENTRY (List, HiddenToolbars, {})
+EXTERN_CFGENTRY (List, RecentFiles)
+EXTERN_CFGENTRY (Bool, DrawAxes)
+EXTERN_CFGENTRY (String, MainColor)
+EXTERN_CFGENTRY (Float, MainColorAlpha)
+EXTERN_CFGENTRY (Bool, DrawWireframe)
+EXTERN_CFGENTRY (Bool, BFCRedGreenView)
+EXTERN_CFGENTRY (Bool, DrawAngles)
+EXTERN_CFGENTRY (Bool, RandomColors)
+EXTERN_CFGENTRY (Bool, DrawSurfaces)
+EXTERN_CFGENTRY (Bool, DrawEdgeLines)
+EXTERN_CFGENTRY (Bool, DrawConditionalLines)
+
+// =============================================================================
+//
+MainWindow::MainWindow (QWidget* parent, Qt::WindowFlags flags) :
+	QMainWindow (parent, flags)
+{
+	g_win = this;
+	ui = new Ui_LDForgeUI;
+	ui->setupUi (this);
+	m_isUpdatingTabs = false;
+	m_renderer = new GLRenderer (this);
+	m_tabBar = new QTabBar;
+	m_tabBar->setTabsClosable (true);
+	ui->verticalLayout->insertWidget (0, m_tabBar);
+
+	// Stuff the renderer into its frame
+	QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame);
+	rendererLayout->addWidget (R());
+
+	connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged()));
+	connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*)));
+	connect (m_tabBar, SIGNAL (currentChanged(int)), this, SLOT (changeCurrentFile()));
+	connect (m_tabBar, SIGNAL (tabCloseRequested (int)), this, SLOT (closeTab (int)));
+
+	if (ActivePrimitiveScanner() != null)
+		connect (ActivePrimitiveScanner(), SIGNAL (workDone()), this, SLOT (updatePrimitives()));
+	else
+		updatePrimitives();
+
+	m_messageLog = new MessageManager;
+	m_messageLog->setRenderer (R());
+	m_renderer->setMessageLog (m_messageLog);
+	m_quickColors = LoadQuickColorList();
+	slot_selectionChanged();
+	setStatusBar (new QStatusBar);
+	updateActions();
+
+	// Connect all actions and save default sequences
+	applyToActions ([&](QAction* act)
+	{
+		connect (act, SIGNAL (triggered()), this, SLOT (actionTriggered()));
+		g_defaultShortcuts[act] = act->shortcut();
+	});
+
+	updateGridToolBar();
+	updateEditModeActions();
+	updateRecentFilesMenu();
+	updateColorToolbar();
+	updateTitle();
+	loadShortcuts (Config::SettingsObject());
+	setMinimumSize (300, 200);
+	connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup()));
+	connect (ui->ringToolHiRes, SIGNAL (clicked (bool)), this, SLOT (ringToolHiResClicked (bool)));
+	connect (ui->ringToolSegments, SIGNAL (valueChanged (int)),
+		this, SLOT (circleToolSegmentsChanged()));
+	circleToolSegmentsChanged(); // invoke it manually for initial label text
+
+	for (QVariant const& toolbarname : cfg::HiddenToolbars)
+	{
+		QToolBar* toolbar = findChild<QToolBar*> (toolbarname.toString());
+
+		if (toolbar != null)
+			toolbar->hide();
+	}
+}
+
+MainWindow::~MainWindow()
+{
+	g_win = null;
+}
+
+// =============================================================================
+//
+void MainWindow::actionTriggered()
+{
+	// Get the name of the sender object and use it to compose the slot name,
+	// then invoke this slot to call the action.
+	QMetaObject::invokeMethod (this,
+		qPrintable (format ("slot_%1", sender()->objectName())), Qt::DirectConnection);
+	endAction();
+}
+
+// =============================================================================
+//
+void MainWindow::endAction()
+{
+	// Add a step in the history now.
+	CurrentDocument()->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 (CurrentDocument());
+	refresh();
+}
+
+// =============================================================================
+//
+void MainWindow::slot_lastSecondCleanup()
+{
+	delete m_renderer;
+	delete ui;
+}
+
+// =============================================================================
+//
+void MainWindow::updateRecentFilesMenu()
+{
+	// First, clear any items in the recent files menu
+for (QAction * recent : m_recentFiles)
+		delete recent;
+
+	m_recentFiles.clear();
+
+	QAction* first = null;
+
+	for (const QVariant& it : cfg::RecentFiles)
+	{
+		QString file = it.toString();
+		QAction* recent = new QAction (GetIcon ("open-recent"), file, this);
+
+		connect (recent, SIGNAL (triggered()), this, SLOT (slot_recentFile()));
+		ui->menuOpenRecent->insertAction (first, recent);
+		m_recentFiles << recent;
+		first = recent;
+	}
+}
+
+// =============================================================================
+//
+QList<LDQuickColor> LoadQuickColorList()
+{
+	QList<LDQuickColor> colors;
+
+	for (QString colorname : cfg::QuickColorToolbar.split (":"))
+	{
+		if (colorname == "|")
+			colors << LDQuickColor::getSeparator();
+		else
+		{
+			LDColor col = LDColor::fromIndex (colorname.toLong());
+
+			if (col != null)
+				colors << LDQuickColor (col, null);
+		}
+	}
+
+	return colors;
+}
+
+// =============================================================================
+//
+void MainWindow::updateColorToolbar()
+{
+	m_colorButtons.clear();
+	ui->toolBarColors->clear();
+	ui->toolBarColors->addAction (ui->actionUncolor);
+	ui->toolBarColors->addSeparator();
+
+	for (LDQuickColor& entry : m_quickColors)
+	{
+		if (entry.isSeparator())
+		{
+			ui->toolBarColors->addSeparator();
+		}
+		else
+		{
+			QToolButton* colorButton = new QToolButton;
+			colorButton->setIcon (MakeColorIcon (entry.color(), 16));
+			colorButton->setIconSize (QSize (16, 16));
+			colorButton->setToolTip (entry.color().name());
+
+			connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor()));
+			ui->toolBarColors->addWidget (colorButton);
+			m_colorButtons << colorButton;
+
+			entry.setToolButton (colorButton);
+		}
+	}
+
+	updateGridToolBar();
+}
+
+// =============================================================================
+//
+void MainWindow::updateGridToolBar()
+{
+	// Ensure that the current grid - and only the current grid - is selected.
+	ui->actionGridCoarse->setChecked (cfg::Grid == Grid::Coarse);
+	ui->actionGridMedium->setChecked (cfg::Grid == Grid::Medium);
+	ui->actionGridFine->setChecked (cfg::Grid == Grid::Fine);
+}
+
+// =============================================================================
+//
+void MainWindow::updateTitle()
+{
+	QString title = format (APPNAME " %1", VersionString());
+
+	// Append our current file if we have one
+	if (CurrentDocument())
+	{
+		title += ": ";
+		title += CurrentDocument()->getDisplayName();
+
+		if (CurrentDocument()->getObjectCount() > 0 and
+			CurrentDocument()->getObject (0)->type() == OBJ_Comment)
+		{
+			// Append title
+			LDCommentPtr comm = CurrentDocument()->getObject (0).staticCast<LDComment>();
+			title += format (": %1", comm->text());
+		}
+
+		if (CurrentDocument()->hasUnsavedChanges())
+			title += '*';
+	}
+
+#ifdef DEBUG
+	title += " [debug build]";
+#elif BUILD_ID != BUILD_RELEASE
+	title += " [pre-release build]";
+#endif // DEBUG
+
+	if (CommitTimeString()[0] != '\0')
+		title += format (" (%1)", QString::fromUtf8 (CommitTimeString()));
+
+	setWindowTitle (title);
+}
+
+// =============================================================================
+//
+int MainWindow::deleteSelection()
+{
+	if (Selection().isEmpty())
+		return 0;
+
+	LDObjectList selCopy = Selection();
+
+	// Delete the objects that were being selected
+	for (LDObjectPtr obj : selCopy)
+		obj->destroy();
+
+	refresh();
+	return selCopy.size();
+}
+
+// =============================================================================
+//
+void MainWindow::buildObjList()
+{
+	if (not CurrentDocument())
+		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 (LDObjectPtr obj : CurrentDocument()->objects())
+	{
+		QString descr;
+
+		switch (obj->type())
+		{
+			case OBJ_Comment:
+			{
+				descr = obj.staticCast<LDComment>()->text();
+
+				// Remove leading whitespace
+				while (descr[0] == ' ')
+					descr.remove (0, 1);
+
+				break;
+			}
+
+			case OBJ_Empty:
+				break; // leave it empty
+
+			case OBJ_Line:
+			case OBJ_Triangle:
+			case OBJ_Quad:
+			case OBJ_CondLine:
+			{
+				for (int i = 0; i < obj->numVertices(); ++i)
+				{
+					if (i != 0)
+						descr += ", ";
+
+					descr += obj->vertex (i).toString (true);
+				}
+				break;
+			}
+
+			case OBJ_Error:
+			{
+				descr = format ("ERROR: %1", obj->asText());
+				break;
+			}
+
+			case OBJ_Subfile:
+			{
+				LDSubfilePtr ref = obj.staticCast<LDSubfile>();
+
+				descr = format ("%1 %2, (", ref->fileInfo()->getDisplayName(), ref->position().toString (true));
+
+				for (int i = 0; i < 9; ++i)
+					descr += format ("%1%2", ref->transform()[i], (i != 8) ? " " : "");
+
+				descr += ')';
+				break;
+			}
+
+			case OBJ_BFC:
+			{
+				descr = LDBFC::StatementStrings[int (obj.staticCast<LDBFC>()->statement())];
+				break;
+			}
+
+			case OBJ_Overlay:
+			{
+				LDOverlayPtr ovl = obj.staticCast<LDOverlay>();
+				descr = format ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->camera()],
+					Basename (ovl->fileName()), ovl->x(), ovl->y(),
+					ovl->width(), ovl->height());
+				break;
+			}
+
+			default:
+			{
+				descr = obj->typeName();
+				break;
+			}
+		}
+
+		QListWidgetItem* item = new QListWidgetItem (descr);
+		item->setIcon (GetIcon (obj->typeName()));
+
+		// Use italic font if hidden
+		if (obj->isHidden())
+		{
+			QFont font = item->font();
+			font.setItalic (true);
+			item->setFont (font);
+		}
+
+		// Color gibberish orange on red so it stands out.
+		if (obj->type() == OBJ_Error)
+		{
+			item->setBackground (QColor ("#AA0000"));
+			item->setForeground (QColor ("#FFAA00"));
+		}
+		elif (cfg::ColorizeObjectsList and obj->isColored() and
+			obj->color() != null and obj->color() != MainColor() and obj->color() != EdgeColor())
+		{
+			// If the object isn't in the main or edge color, draw this
+			// list entry in said color.
+			item->setForeground (obj->color().faceColor());
+		}
+
+		obj->qObjListEntry = item;
+		ui->objectList->insertItem (ui->objectList->count(), item);
+	}
+
+	g_isSelectionLocked = false;
+	updateSelection();
+	scrollToSelection();
+}
+
+// =============================================================================
+//
+void MainWindow::scrollToSelection()
+{
+	if (Selection().isEmpty())
+		return;
+
+	LDObjectPtr obj = Selection().last();
+	ui->objectList->scrollToItem (obj->qObjListEntry);
+}
+
+// =============================================================================
+//
+void MainWindow::slot_selectionChanged()
+{
+	if (g_isSelectionLocked == true or CurrentDocument() == null)
+		return;
+
+	LDObjectList priorSelection = Selection();
+
+	// Get the objects from the object list selection
+	CurrentDocument()->clearSelection();
+	const QList<QListWidgetItem*> items = ui->objectList->selectedItems();
+
+	for (LDObjectPtr obj : CurrentDocument()->objects())
+	{
+		for (QListWidgetItem* item : items)
+		{
+			if (item == obj->qObjListEntry)
+			{
+				obj->select();
+				break;
+			}
+		}
+	}
+
+	// The select() method calls may have selected additional items (i.e. invertnexts)
+	// Update it all now.
+	updateSelection();
+
+	// Update the GL renderer
+	LDObjectList compound = priorSelection + Selection();
+	RemoveDuplicates (compound);
+
+	for (LDObjectPtr obj : compound)
+		R()->compileObject (obj);
+
+	R()->update();
+}
+
+// =============================================================================
+//
+void MainWindow::slot_recentFile()
+{
+	QAction* qAct = static_cast<QAction*> (sender());
+	OpenMainModel (qAct->text());
+}
+
+// =============================================================================
+//
+void MainWindow::slot_quickColor()
+{
+	QToolButton* button = static_cast<QToolButton*> (sender());
+	LDColor col = null;
+
+	for (const LDQuickColor& entry : m_quickColors)
+	{
+		if (entry.toolButton() == button)
+		{
+			col = entry.color();
+			break;
+		}
+	}
+
+	if (col == null)
+		return;
+
+	for (LDObjectPtr obj : Selection())
+	{
+		if (not obj->isColored())
+			continue; // uncolored object
+
+		obj->setColor (col);
+		R()->compileObject (obj);
+	}
+
+	endAction();
+	refresh();
+}
+
+// =============================================================================
+//
+int MainWindow::getInsertionPoint()
+{
+	// If we have a selection, put the item after it.
+	if (not Selection().isEmpty())
+		return Selection().last()->lineNumber() + 1;
+
+	// Otherwise place the object at the end.
+	return CurrentDocument()->getObjectCount();
+}
+
+// =============================================================================
+//
+void MainWindow::doFullRefresh()
+{
+	buildObjList();
+	m_renderer->hardRefresh();
+}
+
+// =============================================================================
+//
+void MainWindow::refresh()
+{
+	buildObjList();
+	m_renderer->update();
+}
+
+// =============================================================================
+//
+void MainWindow::updateSelection()
+{
+	g_isSelectionLocked = true;
+	QItemSelection itemselect;
+	int top = -1;
+	int bottom = -1;
+
+	for (LDObjectPtr obj : Selection())
+	{
+		if (obj->qObjListEntry == null)
+			continue;
+
+		int row = ui->objectList->row (obj->qObjListEntry);
+
+		if (top == -1)
+		{
+			top = bottom = row;
+		}
+		else
+		{
+			if (row != bottom + 1)
+			{
+				itemselect.select (ui->objectList->model()->index (top, 0),
+					ui->objectList->model()->index (bottom, 0));
+				top = -1;
+			}
+
+			bottom = row;
+		}
+	}
+
+	if (top != -1)
+	{
+		itemselect.select (ui->objectList->model()->index (top, 0),
+			ui->objectList->model()->index (bottom, 0));
+	}
+
+	ui->objectList->selectionModel()->select (itemselect, QItemSelectionModel::ClearAndSelect);
+	g_isSelectionLocked = false;
+}
+
+// =============================================================================
+//
+LDColor MainWindow::getSelectedColor()
+{
+	LDColor result;
+
+	for (LDObjectPtr obj : Selection())
+	{
+		if (not obj->isColored())
+			continue; // doesn't use color
+
+		if (result != null and obj->color() != result)
+			return null; // No consensus in object color
+
+		if (result == null)
+			result = obj->color();
+	}
+
+	return result;
+}
+
+// =============================================================================
+//
+void MainWindow::closeEvent (QCloseEvent* ev)
+{
+	// Check whether it's safe to close all files.
+	if (not IsSafeToCloseAll())
+	{
+		ev->ignore();
+		return;
+	}
+
+	// Save the toolbar set
+	cfg::HiddenToolbars.clear();
+
+	for (QToolBar* toolbar : findChildren<QToolBar*>())
+	{
+		if (toolbar->isHidden())
+			cfg::HiddenToolbars << toolbar->objectName();
+	}
+
+	// Save the configuration before leaving.
+	Config::Save();
+	ev->accept();
+}
+
+// =============================================================================
+//
+void MainWindow::spawnContextMenu (const QPoint pos)
+{
+	const bool single = (Selection().size() == 1);
+	LDObjectPtr singleObj = single ? Selection().first() : LDObjectPtr();
+
+	bool hasSubfiles = false;
+
+	for (LDObjectPtr obj : Selection())
+	{
+		if (obj->type() == OBJ_Subfile)
+		{
+			hasSubfiles = true;
+			break;
+		}
+	}
+
+	QMenu* contextMenu = new QMenu;
+
+	if (single and singleObj->type() != OBJ_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);
+
+	if (hasSubfiles)
+	{
+		contextMenu->addSeparator();
+		contextMenu->addAction (ui->actionOpenSubfiles);
+	}
+
+	contextMenu->addSeparator();
+	contextMenu->addAction (ui->actionModeSelect);
+	contextMenu->addAction (ui->actionModeDraw);
+	contextMenu->addAction (ui->actionModeCircle);
+
+	if (not Selection().isEmpty())
+	{
+		contextMenu->addSeparator();
+		contextMenu->addAction (ui->actionSubfileSelection);
+	}
+
+	if (R()->camera() != EFreeCamera)
+	{
+		contextMenu->addSeparator();
+		contextMenu->addAction (ui->actionSetDrawDepth);
+	}
+
+	contextMenu->exec (pos);
+}
+
+// =============================================================================
+//
+void MainWindow::deleteByColor (LDColor color)
+{
+	LDObjectList objs;
+
+	for (LDObjectPtr obj : CurrentDocument()->objects())
+	{
+		if (not obj->isColored() or obj->color() != color)
+			continue;
+
+		objs << obj;
+	}
+
+	for (LDObjectPtr obj : objs)
+		obj->destroy();
+}
+
+// =============================================================================
+//
+void MainWindow::updateEditModeActions()
+{
+	const EditModeType mode = R()->currentEditModeType();
+	ui->actionModeSelect->setChecked (mode == EditModeType::Select);
+	ui->actionModeDraw->setChecked (mode == EditModeType::Draw);
+	ui->actionModeRectangle->setChecked (mode == EditModeType::Rectangle);
+	ui->actionModeCircle->setChecked (mode == EditModeType::Circle);
+	ui->actionModeMagicWand->setChecked (mode == EditModeType::MagicWand);
+	ui->actionModeLinePath->setChecked (mode == EditModeType::LinePath);
+}
+
+// =============================================================================
+//
+void MainWindow::slot_editObject (QListWidgetItem* listitem)
+{
+	for (LDObjectPtr it : CurrentDocument()->objects())
+	{
+		if (it->qObjListEntry == listitem)
+		{
+			AddObjectDialog::staticDialog (it->type(), it);
+			break;
+		}
+	}
+}
+
+// =============================================================================
+//
+bool MainWindow::save (LDDocumentPtr doc, bool saveAs)
+{
+	QString path = doc->fullPath();
+	int64 savesize;
+
+	if (saveAs or path.isEmpty())
+	{
+		QString name = doc->defaultName();
+
+		if (not doc->fullPath().isEmpty()) 
+			name = doc->fullPath();
+		elif (not doc->name().isEmpty())
+			name = doc->name();
+
+		name.replace ("\\", "/");
+		path = QFileDialog::getSaveFileName (g_win, tr ("Save As"),
+			name, tr ("LDraw files (*.dat *.ldr)"));
+
+		if (path.isEmpty())
+		{
+			// User didn't give a file name, abort.
+			return false;
+		}
+	}
+
+	if (doc->save (path, &savesize))
+	{
+		if (doc == CurrentDocument())
+			updateTitle();
+
+		print ("Saved to %1 (%2)", path, MakePrettyFileSize (savesize));
+
+		// Add it to recent files
+		AddRecentFile (path);
+		return true;
+	}
+
+	QString message = format (tr ("Failed to save to %1: %2"), path, strerror (errno));
+
+	// Tell the user the save failed, and give the option for saving as with it.
+	QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win);
+
+	// Add a save-as button
+	QPushButton* saveAsBtn = new QPushButton (tr ("Save As"));
+	saveAsBtn->setIcon (GetIcon ("file-save-as"));
+	dlg.addButton (saveAsBtn, QMessageBox::ActionRole);
+	dlg.setDefaultButton (QMessageBox::Close);
+	dlg.exec();
+
+	if (dlg.clickedButton() == saveAsBtn)
+		return save (doc, true); // yay recursion!
+
+	return false;
+}
+
+void MainWindow::addMessage (QString msg)
+{
+	m_messageLog->addLine (msg);
+}
+
+// ============================================================================
+void ObjectList::contextMenuEvent (QContextMenuEvent* ev)
+{
+	g_win->spawnContextMenu (ev->globalPos());
+}
+
+// =============================================================================
+//
+QPixmap GetIcon (QString iconName)
+{
+	return (QPixmap (format (":/icons/%1.png", iconName)));
+}
+
+// =============================================================================
+//
+bool Confirm (const QString& message)
+{
+	return Confirm (MainWindow::tr ("Confirm"), message);
+}
+
+// =============================================================================
+//
+bool Confirm (const QString& title, const QString& message)
+{
+	return QMessageBox::question (g_win, title, message,
+		(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes;
+}
+
+// =============================================================================
+//
+void Critical (const QString& message)
+{
+	QMessageBox::critical (g_win, MainWindow::tr ("Error"), message,
+		(QMessageBox::Close), QMessageBox::Close);
+}
+
+// =============================================================================
+//
+QIcon MakeColorIcon (LDColor colinfo, const int size)
+{
+	// Create an image object and link a painter to it.
+	QImage img (size, size, QImage::Format_ARGB32);
+	QPainter paint (&img);
+	QColor col = colinfo.faceColor();
+
+	if (colinfo == MainColor())
+	{
+		// Use the user preferences for main color here
+		col = cfg::MainColor;
+		col.setAlphaF (cfg::MainColorAlpha);
+	}
+
+	// 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<LDColor, int> counts;
+
+	for (LDObjectPtr obj : CurrentDocument()->objects())
+	{
+		if (not obj->isColored() or obj->color() == null)
+			continue;
+
+		if (counts.find (obj->color()) == counts.end())
+			counts[obj->color()] = 1;
+		else
+			counts[obj->color()]++;
+	}
+
+	box->clear();
+	int row = 0;
+
+	for (const auto& pair : counts)
+	{
+		QIcon ico = MakeColorIcon (pair.first, 16);
+		box->addItem (ico, format ("[%1] %2 (%3 object%4)",
+			pair.first, pair.first.name(), pair.second, Plural (pair.second)));
+		box->setItemData (row, pair.first.index());
+
+		++row;
+	}
+}
+
+// =============================================================================
+//
+void MainWindow::updateDocumentList()
+{
+	m_isUpdatingTabs = true;
+
+	while (m_tabBar->count() > 0)
+		m_tabBar->removeTab (0);
+
+	for (LDDocumentPtr f : LDDocument::explicitDocuments())
+	{
+		// Add an item to the list for this file and store the tab index
+		// in the document so we can find documents by tab index.
+		f->setTabIndex (m_tabBar->addTab (""));
+		updateDocumentListItem (f);
+	}
+
+	m_isUpdatingTabs = false;
+}
+
+// =============================================================================
+//
+void MainWindow::updateDocumentListItem (LDDocumentPtr doc)
+{
+	bool oldUpdatingTabs = m_isUpdatingTabs;
+	m_isUpdatingTabs = true;
+
+	if (doc->tabIndex() == -1)
+	{
+		// We don't have a list item for this file, so the list either doesn't
+		// exist yet or is out of date. Build the list now.
+		updateDocumentList();
+		return;
+	}
+
+	// If this is the current file, it also needs to be the selected item on
+	// the list.
+	if (doc == CurrentDocument())
+		m_tabBar->setCurrentIndex (doc->tabIndex());
+
+	m_tabBar->setTabText (doc->tabIndex(), doc->getDisplayName());
+
+	// If the document.has unsaved changes, draw a little icon next to it to mark that.
+	m_tabBar->setTabIcon (doc->tabIndex(), doc->hasUnsavedChanges() ? GetIcon ("file-save") : QIcon());
+	m_tabBar->setTabData (doc->tabIndex(), doc->name());
+	m_isUpdatingTabs = oldUpdatingTabs;
+}
+
+// =============================================================================
+//
+// A file is selected from the list of files on the left of the screen. Find out
+// which file was picked and change to it.
+//
+void MainWindow::changeCurrentFile()
+{
+	if (m_isUpdatingTabs)
+		return;
+
+	LDDocumentPtr f;
+	int tabIndex = m_tabBar->currentIndex();
+
+	// Find the file pointer of the item that was selected.
+	for (LDDocumentPtr it : LDDocument::explicitDocuments())
+	{
+		if (it->tabIndex() == tabIndex)
+		{
+			f = it;
+			break;
+		}
+	}
+
+	// If we picked the same file we're currently on, we don't need to do
+	// anything.
+	if (f == null or f == CurrentDocument())
+		return;
+
+	LDDocument::setCurrent (f);
+}
+
+// =============================================================================
+//
+void MainWindow::refreshObjectList()
+{
+#if 0
+	ui->objectList->clear();
+	LDDocumentPtr f = getCurrentDocument();
+
+for (LDObjectPtr obj : *f)
+		ui->objectList->addItem (obj->qObjListEntry);
+
+#endif
+
+	buildObjList();
+}
+
+// =============================================================================
+//
+void MainWindow::updateActions()
+{
+	if (CurrentDocument() != null and CurrentDocument()->history() != null)
+	{
+		History* his = CurrentDocument()->history();
+		int pos = his->position();
+		ui->actionUndo->setEnabled (pos != -1);
+		ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1);
+	}
+
+	ui->actionWireframe->setChecked (cfg::DrawWireframe);
+	ui->actionAxes->setChecked (cfg::DrawAxes);
+	ui->actionBFCView->setChecked (cfg::BFCRedGreenView);
+	ui->actionRandomColors->setChecked (cfg::RandomColors);
+	ui->actionDrawAngles->setChecked (cfg::DrawAngles);
+	ui->actionDrawSurfaces->setChecked (cfg::DrawSurfaces);
+	ui->actionDrawEdgeLines->setChecked (cfg::DrawEdgeLines);
+	ui->actionDrawConditionalLines->setChecked (cfg::DrawConditionalLines);
+}
+
+// =============================================================================
+//
+void MainWindow::updatePrimitives()
+{
+	PopulatePrimitives (ui->primitives);
+}
+
+// =============================================================================
+//
+void MainWindow::closeTab (int tabindex)
+{
+	LDDocumentPtr doc = FindDocument (m_tabBar->tabData (tabindex).toString());
+
+	if (doc == null)
+		return;
+
+	doc->dismiss();
+}
+
+// =============================================================================
+//
+void MainWindow::loadShortcuts (QSettings const* settings)
+{
+	for (QAction* act : findChildren<QAction*>())
+	{
+		QKeySequence seq = settings->value ("shortcut_" + act->objectName(), act->shortcut()).value<QKeySequence>();
+		act->setShortcut (seq);
+	}
+}
+
+// =============================================================================
+//
+void MainWindow::saveShortcuts (QSettings* settings)
+{
+	applyToActions ([&](QAction* act)
+	{
+		QString const key = "shortcut_" + act->objectName();
+
+		if (g_defaultShortcuts[act] != act->shortcut())
+			settings->setValue (key, act->shortcut());
+		else
+			settings->remove (key);
+	});
+}
+
+// =============================================================================
+//
+void MainWindow::applyToActions (std::function<void(QAction*)> function)
+{
+	for (QAction* act : findChildren<QAction*>())
+	{
+		if (not act->objectName().isEmpty())
+			function (act);
+	}
+}
+
+// =============================================================================
+//
+QKeySequence MainWindow::defaultShortcut (QAction* act) // [static]
+{
+	return g_defaultShortcuts[act];
+}
+
+// =============================================================================
+//
+bool MainWindow::ringToolHiRes() const
+{
+	return ui->ringToolHiRes->isChecked();
+}
+
+// =============================================================================
+//
+int MainWindow::ringToolSegments() const
+{
+	return ui->ringToolSegments->value();
+}
+
+// =============================================================================
+//
+void MainWindow::ringToolHiResClicked (bool checked)
+{
+	if (checked)
+	{
+		ui->ringToolSegments->setMaximum (HighResolution);
+		ui->ringToolSegments->setValue (ui->ringToolSegments->value() * 3);
+	}
+	else
+	{
+		ui->ringToolSegments->setValue (ui->ringToolSegments->value() / 3);
+		ui->ringToolSegments->setMaximum (LowResolution);
+	}
+}
+
+// =============================================================================
+//
+void MainWindow::circleToolSegmentsChanged()
+{
+	int numerator (ui->ringToolSegments->value());
+	int denominator (ui->ringToolHiRes->isChecked() ? HighResolution : LowResolution);
+	Simplify (numerator, denominator);
+	ui->ringToolSegmentsLabel->setText (format ("%1 / %2", numerator, denominator));
+}
+
+// =============================================================================
+//
+QImage GetImageFromScreencap (uchar* data, int w, int h)
+{
+	// GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well.
+	return QImage (data, w, h, QImage::Format_ARGB32).rgbSwapped().mirrored();
+}
+
+// =============================================================================
+//
+LDQuickColor::LDQuickColor (LDColor color, QToolButton* toolButton) :
+	m_color (color),
+	m_toolButton (toolButton) {}
+
+// =============================================================================
+//
+LDQuickColor LDQuickColor::getSeparator()
+{
+	return LDQuickColor (null, null);
+}
+
+// =============================================================================
+//
+bool LDQuickColor::isSeparator() const
+{
+	return color() == null;
+}
+
+void PopulatePrimitives (QTreeWidget* tw, QString const& selectByDefault)
+{
+	tw->clear();
+
+	for (PrimitiveCategory* cat : g_PrimitiveCategories)
+	{
+		SubfileListItem* parentItem = new SubfileListItem (tw, null);
+		parentItem->setText (0, cat->name());
+		QList<QTreeWidgetItem*> subfileItems;
+
+		for (Primitive& prim : cat->prims)
+		{
+			SubfileListItem* item = new SubfileListItem (parentItem, &prim);
+			item->setText (0, format ("%1 - %2", prim.name, prim.title));
+			subfileItems << item;
+
+			// If this primitive is the one the current object points to,
+			// select it by default
+			if (selectByDefault == prim.name)
+				tw->setCurrentItem (item);
+		}
+
+		tw->addTopLevelItem (parentItem);
+	}
+}
--- a/src/messageLog.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,136 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QTimer>
-#include <QDate>
-#include "messageLog.h"
-#include "glRenderer.h"
-#include "mainWindow.h"
-
-enum
-{
-	MaxMessages = 5,
-	ExpireTime = 5000,
-	FadeTime = 500
-};
-
-// -------------------------------------------------------------------------------------------------
-//
-MessageManager::MessageManager (QObject* parent) :
-	QObject (parent)
-{
-	m_ticker = new QTimer;
-	m_ticker->start (100);
-	connect (m_ticker, SIGNAL (timeout()), this, SLOT (tick()));
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-MessageManager::Line::Line (QString text) :
-	text (text),
-	alpha (1.0f),
-	expiry (QDateTime::currentDateTime().addMSecs (ExpireTime)) {}
-
-// -------------------------------------------------------------------------------------------------
-//
-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 <= FadeTime)
-	{
-		// Message line has not expired but is fading out
-		alpha = ( (float) msec) / FadeTime;
-		changed = true;
-	}
-
-	return true;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-//	Add a line to the message manager.
-//
-void MessageManager::addLine (QString line)
-{
-	// If there's too many entries, pop the excess out
-	while (m_lines.size() >= MaxMessages)
-		m_lines.removeFirst();
-
-	m_lines << Line (line);
-
-	// Update the renderer view
-	if (renderer())
-		renderer()->update();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-//	Ticks the message manager. All lines are ticked and the renderer scene is redrawn if something
-//	changed.
-//
-void MessageManager::tick()
-{
-	if (m_lines.isEmpty())
-		return;
-
-	bool changed = false;
-
-	for (int i = 0; i < m_lines.size(); ++i)
-	{
-		bool lineChanged;
-
-		if (not m_lines[i].update (lineChanged))
-			m_lines.removeAt (i--);
-
-		changed |= lineChanged;
-	}
-
-	if (changed and renderer())
-		renderer()->update();
-}
-
-// =============================================================================
-//
-const QList<MessageManager::Line>& MessageManager::getLines() const
-{
-	return m_lines;
-}
-
-// =============================================================================
-//
-void PrintToLog (const QString& msg)
-{
-	for (QString& a : msg.split ("\n", QString::SkipEmptyParts))
-	{
-		if (g_win != null)
-			g_win->addMessage (a);
-
-		// Also print it to stdout
-		fprint (stdout, "%1\n", a);
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/messageLog.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,136 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QTimer>
+#include <QDate>
+#include "messageLog.h"
+#include "glRenderer.h"
+#include "mainWindow.h"
+
+enum
+{
+	MaxMessages = 5,
+	ExpireTime = 5000,
+	FadeTime = 500
+};
+
+// -------------------------------------------------------------------------------------------------
+//
+MessageManager::MessageManager (QObject* parent) :
+	QObject (parent)
+{
+	m_ticker = new QTimer;
+	m_ticker->start (100);
+	connect (m_ticker, SIGNAL (timeout()), this, SLOT (tick()));
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+MessageManager::Line::Line (QString text) :
+	text (text),
+	alpha (1.0f),
+	expiry (QDateTime::currentDateTime().addMSecs (ExpireTime)) {}
+
+// -------------------------------------------------------------------------------------------------
+//
+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 <= FadeTime)
+	{
+		// Message line has not expired but is fading out
+		alpha = ( (float) msec) / FadeTime;
+		changed = true;
+	}
+
+	return true;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+//	Add a line to the message manager.
+//
+void MessageManager::addLine (QString line)
+{
+	// If there's too many entries, pop the excess out
+	while (m_lines.size() >= MaxMessages)
+		m_lines.removeFirst();
+
+	m_lines << Line (line);
+
+	// Update the renderer view
+	if (renderer())
+		renderer()->update();
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+//	Ticks the message manager. All lines are ticked and the renderer scene is redrawn if something
+//	changed.
+//
+void MessageManager::tick()
+{
+	if (m_lines.isEmpty())
+		return;
+
+	bool changed = false;
+
+	for (int i = 0; i < m_lines.size(); ++i)
+	{
+		bool lineChanged;
+
+		if (not m_lines[i].update (lineChanged))
+			m_lines.removeAt (i--);
+
+		changed |= lineChanged;
+	}
+
+	if (changed and renderer())
+		renderer()->update();
+}
+
+// =============================================================================
+//
+const QList<MessageManager::Line>& MessageManager::getLines() const
+{
+	return m_lines;
+}
+
+// =============================================================================
+//
+void PrintToLog (const QString& msg)
+{
+	for (QString& a : msg.split ("\n", QString::SkipEmptyParts))
+	{
+		if (g_win != null)
+			g_win->addMessage (a);
+
+		// Also print it to stdout
+		fprint (stdout, "%1\n", a);
+	}
+}
--- a/src/miscallenous.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,306 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <math.h>
-#include <locale.h>
-#include <QColor>
-#include "main.h"
-#include "miscallenous.h"
-#include "mainWindow.h"
-#include "dialogs.h"
-#include "ldDocument.h"
-#include "ui_rotpoint.h"
-
-// Prime number table.
-static const int PrimeNumbers[] =
-{
-	2,    3,    5,    7,    11,   13,   17,   19,   23,   29,
-	31,   37,   41,   43,   47,   53,   59,   61,   67,   71,
-	73,   79,   83,   89,   97,   101,  103,  107,  109,  113,
-	127,  131,  137,  139,  149,  151,  157,  163,  167,  173,
-	179,  181,  191,  193,  197,  199,  211,  223,  227,  229,
-	233,  239,  241,  251,  257,  263,  269,  271,  277,  281,
-	283,  293,  307,  311,  313,  317,  331,  337,  347,  349,
-	353,  359,  367,  373,  379,  383,  389,  397,  401,  409,
-	419,  421,  431,  433,  439,  443,  449,  457,  461,  463,
-	467,  479,  487,  491,  499,  503,  509,  521,  523,  541,
-	547,  557,  563,  569,  571,  577,  587,  593,  599,  601,
-	607,  613,  617,  619,  631,  641,  643,  647,  653,  659,
-	661,  673,  677,  683,  691,  701,  709,  719,  727,  733,
-	739,  743,  751,  757,  761,  769,  773,  787,  797,  809,
-	811,  821,  823,  827,  829,  839,  853,  857,  859,  863,
-	877,  881,  883,  887,  907,  911,  919,  929,  937,  941,
-	947,  953,  967,  971,  977,  983,  991,  997, 1009, 1013,
-	1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
-	1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151,
-	1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
-	1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
-	1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
-	1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451,
-	1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
-	1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583,
-	1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657,
-	1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733,
-	1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811,
-	1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889,
-	1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987,
-	1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053,
-	2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129,
-	2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213,
-	2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287,
-	2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357,
-	2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423,
-	2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531,
-	2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617,
-	2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687,
-	2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741,
-	2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819,
-	2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903,
-	2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999,
-	3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079,
-	3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181,
-	3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257,
-	3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331,
-	3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413,
-	3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511,
-	3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571,
-};
-
-static const long E10[] =
-{
-	1l,
-	10l,
-	100l,
-	1000l,
-	10000l,
-	100000l,
-	1000000l,
-	10000000l,
-	100000000l,
-	1000000000l,
-};
-
-// =============================================================================
-//
-// Grid stuff
-//
-CFGENTRY (Int, Grid, Grid::Medium)
-CFGENTRY (Float, GridCoarseCoordinateSnap, 5.0f)
-CFGENTRY (Float, GridCoarseAngleSnap, 45.0f)
-CFGENTRY (Float, GridMediumCoordinateSnap, 1.0f)
-CFGENTRY (Float, GridMediumAngleSnap, 22.5f)
-CFGENTRY (Float, GridFineCoordinateSnap, 0.1f)
-CFGENTRY (Float, GridFineAngleSnap, 7.5f)
-CFGENTRY (Int, RotationPointType, 0)
-CFGENTRY (Vertex, CustomRotationPoint, Origin)
-
-const GridData Grids[3] =
-{
-	{ "Coarse",	&cfg::GridCoarseCoordinateSnap,	&cfg::GridCoarseAngleSnap	},
-	{ "Medium",	&cfg::GridMediumCoordinateSnap,	&cfg::GridMediumAngleSnap	},
-	{ "Fine",	&cfg::GridFineCoordinateSnap,	&cfg::GridFineAngleSnap	},
-};
-
-// =============================================================================
-//
-// Snap the given coordinate value on the current grid's given axis.
-//
-double Grid::Snap (double value, const Grid::Config type)
-{
-	double snapvalue = (type == Coordinate) ? *CurrentGrid().coordinateSnap : *CurrentGrid().angleSnap;
-	double mult = floor (Abs<double> (value / snapvalue));
-	double out = mult * snapvalue;
-
-	if (Abs (value) - (mult * snapvalue) > snapvalue / 2)
-		out += snapvalue;
-
-	if (value < 0)
-		out = -out;
-
-	return out;
-}
-
-// =============================================================================
-//
-void Simplify (int& numer, int& denom)
-{
-	bool repeat;
-
-	do
-	{
-		repeat = false;
-
-		for (int x = 0; x < countof (PrimeNumbers); x++)
-		{
-			int const prime = PrimeNumbers[x];
-
-			if (numer < prime and denom < prime)
-				break;
-
-			if ((numer % prime == 0) and (denom % prime == 0))
-			{
-				numer /= prime;
-				denom /= prime;
-				repeat = true;
-				break;
-			}
-		}
-	} while (repeat);
-}
-
-// =============================================================================
-//
-Vertex GetRotationPoint (const LDObjectList& objs)
-{
-	switch (RotationPoint (cfg::RotationPointType))
-	{
-		case RotationPoint::ObjectOrigin:
-		{
-			LDBoundingBox box;
-
-			// Calculate center vertex
-			for (LDObjectPtr obj : objs)
-			{
-				if (obj->hasMatrix())
-					box << obj.dynamicCast<LDMatrixObject>()->position();
-				else
-					box << obj;
-			}
-
-			return box.center();
-		}
-
-		case RotationPoint::WorldOrigin:
-			return Origin;
-
-		case RotationPoint::CustomPoint:
-			return cfg::CustomRotationPoint;
-
-		case RotationPoint::NumValues: break;
-	}
-
-	return Vertex();
-}
-
-// =============================================================================
-//
-void ConfigureRotationPoint()
-{
-	QDialog* dlg = new QDialog;
-	Ui::RotPointUI ui;
-	ui.setupUi (dlg);
-
-	switch (RotationPoint (cfg::RotationPointType))
-	{
-		case RotationPoint::ObjectOrigin:
-			ui.objectPoint->setChecked (true);
-			break;
-
-		case RotationPoint::WorldOrigin:
-			ui.worldPoint->setChecked (true);
-			break;
-
-		case RotationPoint::CustomPoint:
-			ui.customPoint->setChecked (true);
-			break;
-
-		case RotationPoint::NumValues: break;
-	}
-
-	ui.customX->setValue (cfg::CustomRotationPoint.x());
-	ui.customY->setValue (cfg::CustomRotationPoint.y());
-	ui.customZ->setValue (cfg::CustomRotationPoint.z());
-
-	if (not dlg->exec())
-		return;
-
-	cfg::RotationPointType = int (
-		(ui.objectPoint->isChecked()) ? RotationPoint::ObjectOrigin :
-		(ui.worldPoint->isChecked())  ? RotationPoint::WorldOrigin :
-		RotationPoint::CustomPoint);
-
-	cfg::CustomRotationPoint.setX (ui.customX->value());
-	cfg::CustomRotationPoint.setY (ui.customY->value());
-	cfg::CustomRotationPoint.setZ (ui.customZ->value());
-}
-
-// =============================================================================
-//
-QString Join (QList<StringFormatArg> vals, QString delim)
-{
-	QStringList list;
-
-	for (const StringFormatArg& arg : vals)
-		list << arg.text();
-
-	return list.join (delim);
-}
-
-// =============================================================================
-//
-void RoundToDecimals (double& a, int decimals)
-{
-	assert (decimals >= 0 and decimals < countof (E10));
-	a = round (a * E10[decimals]) / E10[decimals];
-}
-
-// =============================================================================
-//
-void ApplyToMatrix (Matrix& a, ApplyToMatrixFunction func)
-{
-	for (int i = 0; i < 9; ++i)
-		func (i, a[i]);
-}
-
-// =============================================================================
-//
-void ApplyToMatrix (const Matrix& a, ApplyToMatrixConstFunction func)
-{
-	for (int i = 0; i < 9; ++i)
-		func (i, a[i]);
-}
-
-// =============================================================================
-//
-double GetCoordinateOf (const Vertex& a, Axis ax)
-{
-	switch (ax)
-	{
-		case X: return a.x();
-		case Y: return a.y();
-		case Z: return a.z();
-	}
-
-	assert (false);
-	return 0.0;
-}
-
-
-// =============================================================================
-//
-QString MakePrettyFileSize (qint64 size)
-{
-	if (size < 1024LL)
-		return QString::number (size) + " bytes";
-	else if (size < (1024LL * 1024LL))
-		return QString::number (double (size) / 1024LL, 'f', 1) + " Kb";
-	else if (size < (1024LL * 1024LL * 1024LL))
-		return QString::number (double (size) / (1024LL * 1024LL), 'f', 1) + " Mb";
-	else
-		return QString::number (double (size) / (1024LL * 1024LL * 1024LL), 'f', 1) + " Gb";
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/miscallenous.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,306 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+#include <locale.h>
+#include <QColor>
+#include "main.h"
+#include "miscallenous.h"
+#include "mainWindow.h"
+#include "dialogs.h"
+#include "ldDocument.h"
+#include "ui_rotpoint.h"
+
+// Prime number table.
+static const int PrimeNumbers[] =
+{
+	2,    3,    5,    7,    11,   13,   17,   19,   23,   29,
+	31,   37,   41,   43,   47,   53,   59,   61,   67,   71,
+	73,   79,   83,   89,   97,   101,  103,  107,  109,  113,
+	127,  131,  137,  139,  149,  151,  157,  163,  167,  173,
+	179,  181,  191,  193,  197,  199,  211,  223,  227,  229,
+	233,  239,  241,  251,  257,  263,  269,  271,  277,  281,
+	283,  293,  307,  311,  313,  317,  331,  337,  347,  349,
+	353,  359,  367,  373,  379,  383,  389,  397,  401,  409,
+	419,  421,  431,  433,  439,  443,  449,  457,  461,  463,
+	467,  479,  487,  491,  499,  503,  509,  521,  523,  541,
+	547,  557,  563,  569,  571,  577,  587,  593,  599,  601,
+	607,  613,  617,  619,  631,  641,  643,  647,  653,  659,
+	661,  673,  677,  683,  691,  701,  709,  719,  727,  733,
+	739,  743,  751,  757,  761,  769,  773,  787,  797,  809,
+	811,  821,  823,  827,  829,  839,  853,  857,  859,  863,
+	877,  881,  883,  887,  907,  911,  919,  929,  937,  941,
+	947,  953,  967,  971,  977,  983,  991,  997, 1009, 1013,
+	1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
+	1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151,
+	1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
+	1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
+	1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
+	1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451,
+	1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
+	1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583,
+	1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657,
+	1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733,
+	1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811,
+	1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889,
+	1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987,
+	1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053,
+	2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129,
+	2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213,
+	2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287,
+	2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357,
+	2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423,
+	2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531,
+	2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617,
+	2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687,
+	2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741,
+	2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819,
+	2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903,
+	2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999,
+	3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079,
+	3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181,
+	3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257,
+	3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331,
+	3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413,
+	3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511,
+	3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571,
+};
+
+static const long E10[] =
+{
+	1l,
+	10l,
+	100l,
+	1000l,
+	10000l,
+	100000l,
+	1000000l,
+	10000000l,
+	100000000l,
+	1000000000l,
+};
+
+// =============================================================================
+//
+// Grid stuff
+//
+CFGENTRY (Int, Grid, Grid::Medium)
+CFGENTRY (Float, GridCoarseCoordinateSnap, 5.0f)
+CFGENTRY (Float, GridCoarseAngleSnap, 45.0f)
+CFGENTRY (Float, GridMediumCoordinateSnap, 1.0f)
+CFGENTRY (Float, GridMediumAngleSnap, 22.5f)
+CFGENTRY (Float, GridFineCoordinateSnap, 0.1f)
+CFGENTRY (Float, GridFineAngleSnap, 7.5f)
+CFGENTRY (Int, RotationPointType, 0)
+CFGENTRY (Vertex, CustomRotationPoint, Origin)
+
+const GridData Grids[3] =
+{
+	{ "Coarse",	&cfg::GridCoarseCoordinateSnap,	&cfg::GridCoarseAngleSnap	},
+	{ "Medium",	&cfg::GridMediumCoordinateSnap,	&cfg::GridMediumAngleSnap	},
+	{ "Fine",	&cfg::GridFineCoordinateSnap,	&cfg::GridFineAngleSnap	},
+};
+
+// =============================================================================
+//
+// Snap the given coordinate value on the current grid's given axis.
+//
+double Grid::Snap (double value, const Grid::Config type)
+{
+	double snapvalue = (type == Coordinate) ? *CurrentGrid().coordinateSnap : *CurrentGrid().angleSnap;
+	double mult = floor (Abs<double> (value / snapvalue));
+	double out = mult * snapvalue;
+
+	if (Abs (value) - (mult * snapvalue) > snapvalue / 2)
+		out += snapvalue;
+
+	if (value < 0)
+		out = -out;
+
+	return out;
+}
+
+// =============================================================================
+//
+void Simplify (int& numer, int& denom)
+{
+	bool repeat;
+
+	do
+	{
+		repeat = false;
+
+		for (int x = 0; x < countof (PrimeNumbers); x++)
+		{
+			int const prime = PrimeNumbers[x];
+
+			if (numer < prime and denom < prime)
+				break;
+
+			if ((numer % prime == 0) and (denom % prime == 0))
+			{
+				numer /= prime;
+				denom /= prime;
+				repeat = true;
+				break;
+			}
+		}
+	} while (repeat);
+}
+
+// =============================================================================
+//
+Vertex GetRotationPoint (const LDObjectList& objs)
+{
+	switch (RotationPoint (cfg::RotationPointType))
+	{
+		case RotationPoint::ObjectOrigin:
+		{
+			LDBoundingBox box;
+
+			// Calculate center vertex
+			for (LDObjectPtr obj : objs)
+			{
+				if (obj->hasMatrix())
+					box << obj.dynamicCast<LDMatrixObject>()->position();
+				else
+					box << obj;
+			}
+
+			return box.center();
+		}
+
+		case RotationPoint::WorldOrigin:
+			return Origin;
+
+		case RotationPoint::CustomPoint:
+			return cfg::CustomRotationPoint;
+
+		case RotationPoint::NumValues: break;
+	}
+
+	return Vertex();
+}
+
+// =============================================================================
+//
+void ConfigureRotationPoint()
+{
+	QDialog* dlg = new QDialog;
+	Ui::RotPointUI ui;
+	ui.setupUi (dlg);
+
+	switch (RotationPoint (cfg::RotationPointType))
+	{
+		case RotationPoint::ObjectOrigin:
+			ui.objectPoint->setChecked (true);
+			break;
+
+		case RotationPoint::WorldOrigin:
+			ui.worldPoint->setChecked (true);
+			break;
+
+		case RotationPoint::CustomPoint:
+			ui.customPoint->setChecked (true);
+			break;
+
+		case RotationPoint::NumValues: break;
+	}
+
+	ui.customX->setValue (cfg::CustomRotationPoint.x());
+	ui.customY->setValue (cfg::CustomRotationPoint.y());
+	ui.customZ->setValue (cfg::CustomRotationPoint.z());
+
+	if (not dlg->exec())
+		return;
+
+	cfg::RotationPointType = int (
+		(ui.objectPoint->isChecked()) ? RotationPoint::ObjectOrigin :
+		(ui.worldPoint->isChecked())  ? RotationPoint::WorldOrigin :
+		RotationPoint::CustomPoint);
+
+	cfg::CustomRotationPoint.setX (ui.customX->value());
+	cfg::CustomRotationPoint.setY (ui.customY->value());
+	cfg::CustomRotationPoint.setZ (ui.customZ->value());
+}
+
+// =============================================================================
+//
+QString Join (QList<StringFormatArg> vals, QString delim)
+{
+	QStringList list;
+
+	for (const StringFormatArg& arg : vals)
+		list << arg.text();
+
+	return list.join (delim);
+}
+
+// =============================================================================
+//
+void RoundToDecimals (double& a, int decimals)
+{
+	assert (decimals >= 0 and decimals < countof (E10));
+	a = round (a * E10[decimals]) / E10[decimals];
+}
+
+// =============================================================================
+//
+void ApplyToMatrix (Matrix& a, ApplyToMatrixFunction func)
+{
+	for (int i = 0; i < 9; ++i)
+		func (i, a[i]);
+}
+
+// =============================================================================
+//
+void ApplyToMatrix (const Matrix& a, ApplyToMatrixConstFunction func)
+{
+	for (int i = 0; i < 9; ++i)
+		func (i, a[i]);
+}
+
+// =============================================================================
+//
+double GetCoordinateOf (const Vertex& a, Axis ax)
+{
+	switch (ax)
+	{
+		case X: return a.x();
+		case Y: return a.y();
+		case Z: return a.z();
+	}
+
+	assert (false);
+	return 0.0;
+}
+
+
+// =============================================================================
+//
+QString MakePrettyFileSize (qint64 size)
+{
+	if (size < 1024LL)
+		return QString::number (size) + " bytes";
+	else if (size < (1024LL * 1024LL))
+		return QString::number (double (size) / 1024LL, 'f', 1) + " Kb";
+	else if (size < (1024LL * 1024LL * 1024LL))
+		return QString::number (double (size) / (1024LL * 1024LL), 'f', 1) + " Mb";
+	else
+		return QString::number (double (size) / (1024LL * 1024LL * 1024LL), 'f', 1) + " Gb";
+}
--- a/src/partDownloader.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,591 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu 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 <QFileDialog>
-#include <QMessageBox>
-#include "partDownloader.h"
-#include "ui_downloadfrom.h"
-#include "basics.h"
-#include "mainWindow.h"
-#include "ldDocument.h"
-#include "glRenderer.h"
-
-CFGENTRY (String, DownloadFilePath, "")
-CFGENTRY (Bool, GuessDownloadPaths, true)
-CFGENTRY (Bool, AutoCloseDownloadDialog, true)
-
-const QString g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/");
-
-// =============================================================================
-//
-void PartDownloader::staticBegin()
-{
-	PartDownloader dlg;
-
-	if (not dlg.checkValidPath())
-		return;
-
-	dlg.exec();
-}
-
-// =============================================================================
-//
-QString PartDownloader::getDownloadPath()
-{
-	QString path = cfg::DownloadFilePath;
-
-	if (DIRSLASH[0] != '/')
-		path.replace (DIRSLASH, "/");
-
-	return path;
-}
-
-// =============================================================================
-//
-PartDownloader::PartDownloader (QWidget* parent) :
-	QDialog (parent),
-	m_source (Source (0))
-{
-	setForm (new Ui_DownloadFrom);
-	form()->setupUi (this);
-	form()->fname->setFocus();
-
-#ifdef USE_QT5
-	form()->progress->horizontalHeader()->setSectionResizeMode (QHeaderView::Stretch);
-#else
-	form()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch);
-#endif
-
-	setDownloadButton (new QPushButton (tr ("Download")));
-	form()->buttonBox->addButton (downloadButton(), QDialogButtonBox::ActionRole);
-	getButton (Abort)->setEnabled (false);
-
-	connect (form()->source, SIGNAL (currentIndexChanged (int)),
-		this, SLOT (sourceChanged (int)));
-	connect (form()->buttonBox, SIGNAL (clicked (QAbstractButton*)),
-		this, SLOT (buttonClicked (QAbstractButton*)));
-}
-
-// =============================================================================
-//
-PartDownloader::~PartDownloader()
-{
-	delete form();
-}
-
-// =============================================================================
-//
-bool PartDownloader::checkValidPath()
-{
-	QString path = getDownloadPath();
-
-	if (path.isEmpty() or not QDir (path).exists())
-	{
-		QMessageBox::information(this, "Notice", "Please input a path for files to download.");
-		path = QFileDialog::getExistingDirectory (this, "Path for downloaded files:");
-
-		if (path.isEmpty())
-			return false;
-
-		cfg::DownloadFilePath = path;
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-QString PartDownloader::getURL()
-{
-	const Source src = getSource();
-	QString dest;
-
-	switch (src)
-	{
-		case PartsTracker:
-			dest = form()->fname->text();
-			modifyDestination (dest);
-			form()->fname->setText (dest);
-			return g_unofficialLibraryURL + dest;
-
-		case CustomURL:
-			return form()->fname->text();
-	}
-
-	// Shouldn't happen
-	return "";
-}
-
-// =============================================================================
-//
-void PartDownloader::modifyDestination (QString& dest) const
-{
-	dest = dest.simplified();
-
-	// If the user doesn't want us to guess, stop right here.
-	if (not cfg::GuessDownloadPaths)
-		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) and (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 (Eq (dest.left (2), "s\\", "s/"))
-	{
-		dest.remove (0, 2);
-		dest.prepend ("parts/s/");
-	}
-	elif (Eq (dest.left (3), "48\\", "48/"))
-	{
-		dest.remove (0, 3);
-		dest.prepend ("p/48/");
-	}
-
-	/* Try determine where to put this part. We have four directories:
-	   parts/, parts/s/, p/, and p/48/. If we haven't already specified
-	   either parts/ or p/, we need to add it automatically. Part files
-	   are numbers wit a possible u prefix for parts with unknown number
-	   which can be followed by any of:
-	   - c** (composites)
-	   - d** (formed stickers)
-	   - p** (patterns)
-	   - a lowercase alphabetic letter for variants
-
-	   Subfiles (usually) have an s** prefix, in which case we use parts/s/.
-	   Note that the regex starts with a '^' so it won't catch already fully
-	   given part file names. */
-	QString partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*";
-	QString subpartRegex = partRegex + "s[0-9][0-9]+";
-
-	partRegex += "\\.dat$";
-	subpartRegex += "\\.dat$";
-
-	if (QRegExp (subpartRegex).exactMatch (dest))
-		dest.prepend ("parts/s/");
-	elif (QRegExp (partRegex).exactMatch (dest))
-		dest.prepend ("parts/");
-	elif (not dest.startsWith ("parts/") and not dest.startsWith ("p/"))
-		dest.prepend ("p/");
-}
-
-// =============================================================================
-//
-PartDownloader::Source PartDownloader::getSource() const
-{
-	return m_source;
-}
-
-// =============================================================================
-//
-void PartDownloader::setSource (Source src)
-{
-	m_source = src;
-	form()->source->setCurrentIndex (int (src));
-}
-
-// =============================================================================
-//
-void PartDownloader::sourceChanged (int i)
-{
-	if (i == CustomURL)
-		form()->fileNameLabel->setText (tr ("URL:"));
-	else
-		form()->fileNameLabel->setText (tr ("File name:"));
-
-	m_source = Source (i);
-}
-
-// =============================================================================
-//
-void PartDownloader::buttonClicked (QAbstractButton* btn)
-{
-	if (btn == getButton (Close))
-	{
-		reject();
-	}
-	elif (btn == getButton (Abort))
-	{
-		setAborted (true);
-
-		for (PartDownloadRequest* req : requests())
-			req->abort();
-	}
-	elif (btn == getButton (Download))
-	{
-		QString dest = form()->fname->text();
-		setPrimaryFile (LDDocumentPtr());
-		setAborted (false);
-
-		if (getSource() == CustomURL)
-			dest = Basename (getURL());
-
-		modifyDestination (dest);
-
-		if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest))
-		{
-			const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest);
-			if (not Confirm (tr ("Overwrite?"), overwritemsg))
-				return;
-		}
-
-		downloadFile (dest, getURL(), true);
-	}
-}
-
-// =============================================================================
-//
-void PartDownloader::downloadFile (QString dest, QString url, bool primary)
-{
-	const int row = form()->progress->rowCount();
-
-	// Don't download files repeadetly.
-	if (filesToDownload().indexOf (dest) != -1)
-		return;
-
-	print ("Downloading %1 from %2\n", dest, url);
-	modifyDestination (dest);
-	PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this);
-	m_filesToDownload << dest;
-	m_requests << req;
-	form()->progress->insertRow (row);
-	req->setTableRow (row);
-	req->updateToTable();
-	downloadButton()->setEnabled (false);
-	form()->progress->setEnabled (true);
-	form()->fname->setEnabled (false);
-	form()->source->setEnabled (false);
-	getButton (Close)->setEnabled (false);
-	getButton (Abort)->setEnabled (true);
-	getButton (Download)->setEnabled (false);
-}
-
-// =============================================================================
-//
-void PartDownloader::downloadFromPartsTracker (QString file)
-{
-	modifyDestination (file);
-	downloadFile (file, g_unofficialLibraryURL + file, false);
-}
-
-// =============================================================================
-//
-void PartDownloader::checkIfFinished()
-{
-	bool failed = isAborted();
-
-	// If there is some download still working, we're not finished.
-	for (PartDownloadRequest* req : requests())
-	{
-		if (not req->isFinished())
-			return;
-
-        if (req->state() == PartDownloadRequest::State::Failed)
-			failed = true;
-	}
-
-	for (PartDownloadRequest* req : requests())
-		delete req;
-
-	m_requests.clear();
-
-	// Update everything now
-	if (primaryFile() != null)
-	{
-		LDDocument::setCurrent (primaryFile());
-		g_win->doFullRefresh();
-		g_win->R()->resetAngles();
-	}
-
-		for (LDDocumentPtr f : m_files)
-		f->reloadAllSubfiles();
-
-	if (cfg::AutoCloseDownloadDialog and not failed)
-	{
-		// Close automatically if desired.
-		accept();
-	}
-	else
-	{
-		// Allow the prompt be closed now.
-		getButton (Abort)->setEnabled (false);
-		getButton (Close)->setEnabled (true);
-	}
-}
-
-// =============================================================================
-//
-QPushButton* PartDownloader::getButton (PartDownloader::Button i)
-{
-	switch (i)
-	{
-		case Download:
-			return downloadButton();
-
-		case Abort:
-			return qobject_cast<QPushButton*> (form()->buttonBox->button (QDialogButtonBox::Abort));
-
-		case Close:
-			return qobject_cast<QPushButton*> (form()->buttonBox->button (QDialogButtonBox::Close));
-	}
-
-	return null;
-}
-
-// =============================================================================
-//
-PartDownloadRequest::PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent) :
-	QObject (parent),
-    m_state (State::Requesting),
-	m_prompt (parent),
-	m_url (url),
-	m_destinaton (dest),
-	m_filePath (PartDownloader::getDownloadPath() + DIRSLASH + dest),
-	m_networkManager (new QNetworkAccessManager),
-	m_isFirstUpdate (true),
-	m_isPrimary (primary),
-	m_filePointer (null)
-{
-	// Make sure that we have a valid destination.
-	QString dirpath = Dirname (filePath());
-
-	QDir dir (dirpath);
-
-	if (not dir.exists())
-	{
-		print ("Creating %1...\n", dirpath);
-
-		if (not dir.mkpath (dirpath))
-			Critical (format (tr ("Couldn't create the directory %1!"), dirpath));
-	}
-
-	setNetworkReply (networkManager()->get (QNetworkRequest (QUrl (url))));
-	connect (networkReply(), SIGNAL (finished()), this, SLOT (downloadFinished()));
-	connect (networkReply(), SIGNAL (readyRead()), this, SLOT (readyRead()));
-	connect (networkReply(), SIGNAL (downloadProgress (qint64, qint64)),
-		this, SLOT (downloadProgress (qint64, qint64)));
-}
-
-// =============================================================================
-//
-PartDownloadRequest::~PartDownloadRequest() {}
-
-// =============================================================================
-//
-void PartDownloadRequest::updateToTable()
-{
-	int const labelcol = PartDownloader::PartLabelColumn;
-	int const progcol = PartDownloader::ProgressColumn;
-	QTableWidget* table = prompt()->form()->progress;
-	QProgressBar* prog;
-
-	switch (state())
-	{
-        case State::Requesting:
-        case State::Downloading:
-		{
-			prog = qobject_cast<QProgressBar*> (table->cellWidget (tableRow(), progcol));
-
-			if (not prog)
-			{
-				prog = new QProgressBar;
-				table->setCellWidget (tableRow(), progcol, prog);
-			}
-
-			prog->setRange (0, numBytesTotal());
-			prog->setValue (numBytesRead());
-		} break;
-
-        case State::Finished:
-        case State::Failed:
-		{
-            const QString text = (state() == State::Finished)
-				? "<b><span style=\"color: #080\">FINISHED</span></b>"
-				: "<b><span style=\"color: #800\">FAILED</span></b>";
-
-			QLabel* lb = new QLabel (text);
-			lb->setAlignment (Qt::AlignCenter);
-			table->setCellWidget (tableRow(), progcol, lb);
-		} break;
-	}
-
-	QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (tableRow(), labelcol));
-
-	if (isFirstUpdate())
-	{
-		lb = new QLabel (format ("<b>%1</b>", destinaton()), table);
-		table->setCellWidget (tableRow(), labelcol, lb);
-	}
-
-	// Make sure that the cell is big enough to contain the label
-	if (table->columnWidth (labelcol) < lb->width())
-		table->setColumnWidth (labelcol, lb->width());
-
-	setFirstUpdate (true);
-}
-
-// =============================================================================
-//
-void PartDownloadRequest::downloadFinished()
-{
-	if (networkReply()->error() != QNetworkReply::NoError)
-	{
-		if (isPrimary() and not prompt()->isAborted())
-			Critical (networkReply()->errorString());
-
-		print ("Unable to download %1: %2\n", m_destinaton, networkReply()->errorString());
-        setState (State::Failed);
-	}
-    elif (state() != State::Failed)
-	{
-        setState (State::Finished);
-	}
-
-	setNumBytesRead (numBytesTotal());
-	updateToTable();
-
-	if (filePointer())
-	{
-		filePointer()->close();
-		delete filePointer();
-		setFilePointer (null);
-
-        if (state() == State::Failed)
-			QFile::remove (filePath());
-	}
-
-    if (state() != State::Finished)
-	{
-		prompt()->checkIfFinished();
-		return;
-	}
-
-	// Try to load this file now.
-	LDDocumentPtr f = OpenDocument (filePath(), false, not isPrimary());
-
-	if (f == null)
-		return;
-
-	// 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 (LDObjectPtr obj : f->objects())
-	{
-		LDErrorPtr err = obj.dynamicCast<LDError>();
-
-		if ((err == null) or (err->fileReferenced().isEmpty()))
-			continue;
-
-		QString dest = err->fileReferenced();
-		prompt()->downloadFromPartsTracker (dest);
-	}
-
-	prompt()->addFile (f);
-
-	if (isPrimary())
-	{
-		AddRecentFile (filePath());
-		prompt()->setPrimaryFile (f);
-	}
-
-	prompt()->checkIfFinished();
-}
-
-// =============================================================================
-//
-void PartDownloader::addFile (LDDocumentPtr f)
-{
-	m_files << f;
-}
-
-// =============================================================================
-//
-void PartDownloadRequest::downloadProgress (int64 recv, int64 total)
-{
-	setNumBytesRead (recv);
-	setNumBytesTotal (total);
-    setState (State::Downloading);
-	updateToTable();
-}
-
-// =============================================================================
-//
-void PartDownloadRequest::readyRead()
-{
-    if (state() == State::Failed)
-		return;
-
-	if (filePointer() == null)
-	{
-		m_filePath.replace ("\\", "/");
-
-		// We have already asked the user whether we can overwrite so we're good
-		// to go here.
-		setFilePointer (new QFile (filePath().toLocal8Bit()));
-
-		if (not filePointer()->open (QIODevice::WriteOnly))
-		{
-			Critical (format (tr ("Couldn't open %1 for writing: %2"), filePath(), strerror (errno)));
-            setState (State::Failed);
-			networkReply()->abort();
-			updateToTable();
-			prompt()->checkIfFinished();
-			return;
-		}
-	}
-
-	filePointer()->write (networkReply()->readAll());
-}
-
-// =============================================================================
-//
-bool PartDownloadRequest::isFinished() const
-{
-    return Eq (state(), State::Finished, State::Failed);
-}
-
-// =============================================================================
-//
-void PartDownloadRequest::abort()
-{
-	networkReply()->abort();
-}
-
-// =============================================================================
-//
-void MainWindow::actionDownloadFrom()
-{
-	PartDownloader::staticBegin();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/partDownloader.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,591 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu 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 <QFileDialog>
+#include <QMessageBox>
+#include "partDownloader.h"
+#include "ui_downloadfrom.h"
+#include "basics.h"
+#include "mainWindow.h"
+#include "ldDocument.h"
+#include "glRenderer.h"
+
+CFGENTRY (String, DownloadFilePath, "")
+CFGENTRY (Bool, GuessDownloadPaths, true)
+CFGENTRY (Bool, AutoCloseDownloadDialog, true)
+
+const QString g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/");
+
+// =============================================================================
+//
+void PartDownloader::staticBegin()
+{
+	PartDownloader dlg;
+
+	if (not dlg.checkValidPath())
+		return;
+
+	dlg.exec();
+}
+
+// =============================================================================
+//
+QString PartDownloader::getDownloadPath()
+{
+	QString path = cfg::DownloadFilePath;
+
+	if (DIRSLASH[0] != '/')
+		path.replace (DIRSLASH, "/");
+
+	return path;
+}
+
+// =============================================================================
+//
+PartDownloader::PartDownloader (QWidget* parent) :
+	QDialog (parent),
+	m_source (Source (0))
+{
+	setForm (new Ui_DownloadFrom);
+	form()->setupUi (this);
+	form()->fname->setFocus();
+
+#ifdef USE_QT5
+	form()->progress->horizontalHeader()->setSectionResizeMode (QHeaderView::Stretch);
+#else
+	form()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch);
+#endif
+
+	setDownloadButton (new QPushButton (tr ("Download")));
+	form()->buttonBox->addButton (downloadButton(), QDialogButtonBox::ActionRole);
+	getButton (Abort)->setEnabled (false);
+
+	connect (form()->source, SIGNAL (currentIndexChanged (int)),
+		this, SLOT (sourceChanged (int)));
+	connect (form()->buttonBox, SIGNAL (clicked (QAbstractButton*)),
+		this, SLOT (buttonClicked (QAbstractButton*)));
+}
+
+// =============================================================================
+//
+PartDownloader::~PartDownloader()
+{
+	delete form();
+}
+
+// =============================================================================
+//
+bool PartDownloader::checkValidPath()
+{
+	QString path = getDownloadPath();
+
+	if (path.isEmpty() or not QDir (path).exists())
+	{
+		QMessageBox::information(this, "Notice", "Please input a path for files to download.");
+		path = QFileDialog::getExistingDirectory (this, "Path for downloaded files:");
+
+		if (path.isEmpty())
+			return false;
+
+		cfg::DownloadFilePath = path;
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+QString PartDownloader::getURL()
+{
+	const Source src = getSource();
+	QString dest;
+
+	switch (src)
+	{
+		case PartsTracker:
+			dest = form()->fname->text();
+			modifyDestination (dest);
+			form()->fname->setText (dest);
+			return g_unofficialLibraryURL + dest;
+
+		case CustomURL:
+			return form()->fname->text();
+	}
+
+	// Shouldn't happen
+	return "";
+}
+
+// =============================================================================
+//
+void PartDownloader::modifyDestination (QString& dest) const
+{
+	dest = dest.simplified();
+
+	// If the user doesn't want us to guess, stop right here.
+	if (not cfg::GuessDownloadPaths)
+		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) and (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 (Eq (dest.left (2), "s\\", "s/"))
+	{
+		dest.remove (0, 2);
+		dest.prepend ("parts/s/");
+	}
+	elif (Eq (dest.left (3), "48\\", "48/"))
+	{
+		dest.remove (0, 3);
+		dest.prepend ("p/48/");
+	}
+
+	/* Try determine where to put this part. We have four directories:
+	   parts/, parts/s/, p/, and p/48/. If we haven't already specified
+	   either parts/ or p/, we need to add it automatically. Part files
+	   are numbers wit a possible u prefix for parts with unknown number
+	   which can be followed by any of:
+	   - c** (composites)
+	   - d** (formed stickers)
+	   - p** (patterns)
+	   - a lowercase alphabetic letter for variants
+
+	   Subfiles (usually) have an s** prefix, in which case we use parts/s/.
+	   Note that the regex starts with a '^' so it won't catch already fully
+	   given part file names. */
+	QString partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*";
+	QString subpartRegex = partRegex + "s[0-9][0-9]+";
+
+	partRegex += "\\.dat$";
+	subpartRegex += "\\.dat$";
+
+	if (QRegExp (subpartRegex).exactMatch (dest))
+		dest.prepend ("parts/s/");
+	elif (QRegExp (partRegex).exactMatch (dest))
+		dest.prepend ("parts/");
+	elif (not dest.startsWith ("parts/") and not dest.startsWith ("p/"))
+		dest.prepend ("p/");
+}
+
+// =============================================================================
+//
+PartDownloader::Source PartDownloader::getSource() const
+{
+	return m_source;
+}
+
+// =============================================================================
+//
+void PartDownloader::setSource (Source src)
+{
+	m_source = src;
+	form()->source->setCurrentIndex (int (src));
+}
+
+// =============================================================================
+//
+void PartDownloader::sourceChanged (int i)
+{
+	if (i == CustomURL)
+		form()->fileNameLabel->setText (tr ("URL:"));
+	else
+		form()->fileNameLabel->setText (tr ("File name:"));
+
+	m_source = Source (i);
+}
+
+// =============================================================================
+//
+void PartDownloader::buttonClicked (QAbstractButton* btn)
+{
+	if (btn == getButton (Close))
+	{
+		reject();
+	}
+	elif (btn == getButton (Abort))
+	{
+		setAborted (true);
+
+		for (PartDownloadRequest* req : requests())
+			req->abort();
+	}
+	elif (btn == getButton (Download))
+	{
+		QString dest = form()->fname->text();
+		setPrimaryFile (LDDocumentPtr());
+		setAborted (false);
+
+		if (getSource() == CustomURL)
+			dest = Basename (getURL());
+
+		modifyDestination (dest);
+
+		if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest))
+		{
+			const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest);
+			if (not Confirm (tr ("Overwrite?"), overwritemsg))
+				return;
+		}
+
+		downloadFile (dest, getURL(), true);
+	}
+}
+
+// =============================================================================
+//
+void PartDownloader::downloadFile (QString dest, QString url, bool primary)
+{
+	const int row = form()->progress->rowCount();
+
+	// Don't download files repeadetly.
+	if (filesToDownload().indexOf (dest) != -1)
+		return;
+
+	print ("Downloading %1 from %2\n", dest, url);
+	modifyDestination (dest);
+	PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this);
+	m_filesToDownload << dest;
+	m_requests << req;
+	form()->progress->insertRow (row);
+	req->setTableRow (row);
+	req->updateToTable();
+	downloadButton()->setEnabled (false);
+	form()->progress->setEnabled (true);
+	form()->fname->setEnabled (false);
+	form()->source->setEnabled (false);
+	getButton (Close)->setEnabled (false);
+	getButton (Abort)->setEnabled (true);
+	getButton (Download)->setEnabled (false);
+}
+
+// =============================================================================
+//
+void PartDownloader::downloadFromPartsTracker (QString file)
+{
+	modifyDestination (file);
+	downloadFile (file, g_unofficialLibraryURL + file, false);
+}
+
+// =============================================================================
+//
+void PartDownloader::checkIfFinished()
+{
+	bool failed = isAborted();
+
+	// If there is some download still working, we're not finished.
+	for (PartDownloadRequest* req : requests())
+	{
+		if (not req->isFinished())
+			return;
+
+        if (req->state() == PartDownloadRequest::State::Failed)
+			failed = true;
+	}
+
+	for (PartDownloadRequest* req : requests())
+		delete req;
+
+	m_requests.clear();
+
+	// Update everything now
+	if (primaryFile() != null)
+	{
+		LDDocument::setCurrent (primaryFile());
+		g_win->doFullRefresh();
+		g_win->R()->resetAngles();
+	}
+
+		for (LDDocumentPtr f : m_files)
+		f->reloadAllSubfiles();
+
+	if (cfg::AutoCloseDownloadDialog and not failed)
+	{
+		// Close automatically if desired.
+		accept();
+	}
+	else
+	{
+		// Allow the prompt be closed now.
+		getButton (Abort)->setEnabled (false);
+		getButton (Close)->setEnabled (true);
+	}
+}
+
+// =============================================================================
+//
+QPushButton* PartDownloader::getButton (PartDownloader::Button i)
+{
+	switch (i)
+	{
+		case Download:
+			return downloadButton();
+
+		case Abort:
+			return qobject_cast<QPushButton*> (form()->buttonBox->button (QDialogButtonBox::Abort));
+
+		case Close:
+			return qobject_cast<QPushButton*> (form()->buttonBox->button (QDialogButtonBox::Close));
+	}
+
+	return null;
+}
+
+// =============================================================================
+//
+PartDownloadRequest::PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent) :
+	QObject (parent),
+    m_state (State::Requesting),
+	m_prompt (parent),
+	m_url (url),
+	m_destinaton (dest),
+	m_filePath (PartDownloader::getDownloadPath() + DIRSLASH + dest),
+	m_networkManager (new QNetworkAccessManager),
+	m_isFirstUpdate (true),
+	m_isPrimary (primary),
+	m_filePointer (null)
+{
+	// Make sure that we have a valid destination.
+	QString dirpath = Dirname (filePath());
+
+	QDir dir (dirpath);
+
+	if (not dir.exists())
+	{
+		print ("Creating %1...\n", dirpath);
+
+		if (not dir.mkpath (dirpath))
+			Critical (format (tr ("Couldn't create the directory %1!"), dirpath));
+	}
+
+	setNetworkReply (networkManager()->get (QNetworkRequest (QUrl (url))));
+	connect (networkReply(), SIGNAL (finished()), this, SLOT (downloadFinished()));
+	connect (networkReply(), SIGNAL (readyRead()), this, SLOT (readyRead()));
+	connect (networkReply(), SIGNAL (downloadProgress (qint64, qint64)),
+		this, SLOT (downloadProgress (qint64, qint64)));
+}
+
+// =============================================================================
+//
+PartDownloadRequest::~PartDownloadRequest() {}
+
+// =============================================================================
+//
+void PartDownloadRequest::updateToTable()
+{
+	int const labelcol = PartDownloader::PartLabelColumn;
+	int const progcol = PartDownloader::ProgressColumn;
+	QTableWidget* table = prompt()->form()->progress;
+	QProgressBar* prog;
+
+	switch (state())
+	{
+        case State::Requesting:
+        case State::Downloading:
+		{
+			prog = qobject_cast<QProgressBar*> (table->cellWidget (tableRow(), progcol));
+
+			if (not prog)
+			{
+				prog = new QProgressBar;
+				table->setCellWidget (tableRow(), progcol, prog);
+			}
+
+			prog->setRange (0, numBytesTotal());
+			prog->setValue (numBytesRead());
+		} break;
+
+        case State::Finished:
+        case State::Failed:
+		{
+            const QString text = (state() == State::Finished)
+				? "<b><span style=\"color: #080\">FINISHED</span></b>"
+				: "<b><span style=\"color: #800\">FAILED</span></b>";
+
+			QLabel* lb = new QLabel (text);
+			lb->setAlignment (Qt::AlignCenter);
+			table->setCellWidget (tableRow(), progcol, lb);
+		} break;
+	}
+
+	QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (tableRow(), labelcol));
+
+	if (isFirstUpdate())
+	{
+		lb = new QLabel (format ("<b>%1</b>", destinaton()), table);
+		table->setCellWidget (tableRow(), labelcol, lb);
+	}
+
+	// Make sure that the cell is big enough to contain the label
+	if (table->columnWidth (labelcol) < lb->width())
+		table->setColumnWidth (labelcol, lb->width());
+
+	setFirstUpdate (true);
+}
+
+// =============================================================================
+//
+void PartDownloadRequest::downloadFinished()
+{
+	if (networkReply()->error() != QNetworkReply::NoError)
+	{
+		if (isPrimary() and not prompt()->isAborted())
+			Critical (networkReply()->errorString());
+
+		print ("Unable to download %1: %2\n", m_destinaton, networkReply()->errorString());
+        setState (State::Failed);
+	}
+    elif (state() != State::Failed)
+	{
+        setState (State::Finished);
+	}
+
+	setNumBytesRead (numBytesTotal());
+	updateToTable();
+
+	if (filePointer())
+	{
+		filePointer()->close();
+		delete filePointer();
+		setFilePointer (null);
+
+        if (state() == State::Failed)
+			QFile::remove (filePath());
+	}
+
+    if (state() != State::Finished)
+	{
+		prompt()->checkIfFinished();
+		return;
+	}
+
+	// Try to load this file now.
+	LDDocumentPtr f = OpenDocument (filePath(), false, not isPrimary());
+
+	if (f == null)
+		return;
+
+	// 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 (LDObjectPtr obj : f->objects())
+	{
+		LDErrorPtr err = obj.dynamicCast<LDError>();
+
+		if ((err == null) or (err->fileReferenced().isEmpty()))
+			continue;
+
+		QString dest = err->fileReferenced();
+		prompt()->downloadFromPartsTracker (dest);
+	}
+
+	prompt()->addFile (f);
+
+	if (isPrimary())
+	{
+		AddRecentFile (filePath());
+		prompt()->setPrimaryFile (f);
+	}
+
+	prompt()->checkIfFinished();
+}
+
+// =============================================================================
+//
+void PartDownloader::addFile (LDDocumentPtr f)
+{
+	m_files << f;
+}
+
+// =============================================================================
+//
+void PartDownloadRequest::downloadProgress (int64 recv, int64 total)
+{
+	setNumBytesRead (recv);
+	setNumBytesTotal (total);
+    setState (State::Downloading);
+	updateToTable();
+}
+
+// =============================================================================
+//
+void PartDownloadRequest::readyRead()
+{
+    if (state() == State::Failed)
+		return;
+
+	if (filePointer() == null)
+	{
+		m_filePath.replace ("\\", "/");
+
+		// We have already asked the user whether we can overwrite so we're good
+		// to go here.
+		setFilePointer (new QFile (filePath().toLocal8Bit()));
+
+		if (not filePointer()->open (QIODevice::WriteOnly))
+		{
+			Critical (format (tr ("Couldn't open %1 for writing: %2"), filePath(), strerror (errno)));
+            setState (State::Failed);
+			networkReply()->abort();
+			updateToTable();
+			prompt()->checkIfFinished();
+			return;
+		}
+	}
+
+	filePointer()->write (networkReply()->readAll());
+}
+
+// =============================================================================
+//
+bool PartDownloadRequest::isFinished() const
+{
+    return Eq (state(), State::Finished, State::Failed);
+}
+
+// =============================================================================
+//
+void PartDownloadRequest::abort()
+{
+	networkReply()->abort();
+}
+
+// =============================================================================
+//
+void MainWindow::actionDownloadFrom()
+{
+	PartDownloader::staticBegin();
+}
--- a/src/primitives.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,715 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDir>
-#include <QRegExp>
-#include <QFileDialog>
-#include "ldDocument.h"
-#include "mainWindow.h"
-#include "primitives.h"
-#include "ui_makeprim.h"
-#include "miscallenous.h"
-#include "colors.h"
-
-QList<PrimitiveCategory*> g_PrimitiveCategories;
-QList<Primitive> g_primitives;
-static PrimitiveScanner* g_activeScanner = null;
-PrimitiveCategory* g_unmatched = null;
-
-EXTERN_CFGENTRY (String, DefaultName)
-EXTERN_CFGENTRY (String, DefaultUser)
-EXTERN_CFGENTRY (Int, DefaultLicense)
-
-static const QStringList g_radialNameRoots =
-{
-	"edge",
-	"cyli",
-	"disc",
-	"ndis",
-	"ring",
-	"con"
-};
-
-PrimitiveScanner* ActivePrimitiveScanner()
-{
-	return g_activeScanner;
-}
-
-// =============================================================================
-//
-void LoadPrimitives()
-{
-	// Try to load prims.cfg
-	QFile conf (Config::FilePath ("prims.cfg"));
-
-	if (not conf.open (QIODevice::ReadOnly))
-	{
-		// No prims.cfg, build it
-		PrimitiveScanner::start();
-	}
-	else
-	{
-		while (not conf.atEnd())
-		{
-			QString line = conf.readLine();
-
-			if (line.endsWith ("\n"))
-				line.chop (1);
-
-			if (line.endsWith ("\r"))
-				line.chop (1);
-
-			int space = line.indexOf (" ");
-
-			if (space == -1)
-				continue;
-
-			Primitive info;
-			info.name = line.left (space);
-			info.title = line.mid (space + 1);
-			g_primitives << info;
-		}
-
-		PrimitiveCategory::populateCategories();
-		print ("%1 primitives loaded.\n", g_primitives.size());
-	}
-}
-
-// =============================================================================
-//
-static void GetRecursiveFilenames (QDir dir, QList<QString>& fnames)
-{
-	QFileInfoList flist = dir.entryInfoList (QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
-
-	for (const QFileInfo& info : flist)
-	{
-		if (info.isDir())
-			GetRecursiveFilenames (QDir (info.absoluteFilePath()), fnames);
-		else
-			fnames << info.absoluteFilePath();
-	}
-}
-
-// =============================================================================
-//
-PrimitiveScanner::PrimitiveScanner (QObject* parent) :
-	QObject (parent),
-	m_i (0)
-{
-	g_activeScanner = this;
-	QDir dir (LDPaths::prims());
-	assert (dir.exists());
-	m_baselen = dir.absolutePath().length();
-	GetRecursiveFilenames (dir, m_files);
-	emit starting (m_files.size());
-	print ("Scanning primitives...");
-}
-
-// =============================================================================
-//
-PrimitiveScanner::~PrimitiveScanner()
-{
-	g_activeScanner = null;
-}
-
-// =============================================================================
-//
-void PrimitiveScanner::work()
-{
-	int j = Min (m_i + 100, m_files.size());
-
-	for (; m_i < j; ++m_i)
-	{
-		QString fname = m_files[m_i];
-		QFile f (fname);
-
-		if (not f.open (QIODevice::ReadOnly))
-			continue;
-
-		Primitive info;
-		info.name = fname.mid (m_baselen + 1);  // make full path relative
-		info.name.replace ('/', '\\');  // use DOS backslashes, they're expected
-		info.category = null;
-		QByteArray titledata = f.readLine();
-
-		if (titledata != QByteArray())
-			info.title = QString::fromUtf8 (titledata);
-
-		info.title = info.title.simplified();
-
-		if (Q_LIKELY (info.title[0] == '0'))
-		{
-			info.title.remove (0, 1);  // remove 0
-			info.title = info.title.simplified();
-		}
-
-		m_prims << info;
-	}
-
-	if (m_i == m_files.size())
-	{
-		// Done with primitives, now save to a config file
-		QString path = Config::FilePath ("prims.cfg");
-		QFile conf (path);
-
-		if (not conf.open (QIODevice::WriteOnly | QIODevice::Text))
-			Critical (format ("Couldn't write primitive list %1: %2",
-				path, conf.errorString()));
-		else
-		{
-			for (Primitive& info : m_prims)
-				fprint (conf, "%1 %2\r\n", info.name, info.title);
-
-			conf.close();
-		}
-
-		g_primitives = m_prims;
-		PrimitiveCategory::populateCategories();
-		print ("%1 primitives scanned", g_primitives.size());
-		g_activeScanner = null;
-		emit workDone();
-		deleteLater();
-	}
-	else
-	{
-		// Defer to event loop, pick up the work later
-		emit update (m_i);
-		QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection);
-	}
-}
-
-// =============================================================================
-//
-void PrimitiveScanner::start()
-{
-	if (g_activeScanner)
-		return;
-
-	PrimitiveCategory::loadCategories();
-	PrimitiveScanner* scanner = new PrimitiveScanner;
-	scanner->work();
-}
-
-// =============================================================================
-//
-PrimitiveCategory::PrimitiveCategory (QString name, QObject* parent) :
-	QObject (parent),
-	m_name (name) {}
-
-// =============================================================================
-//
-void PrimitiveCategory::populateCategories()
-{
-	loadCategories();
-
-	for (PrimitiveCategory* cat : g_PrimitiveCategories)
-		cat->prims.clear();
-
-	for (Primitive& prim : g_primitives)
-	{
-		bool matched = false;
-		prim.category = null;
-
-		// Go over the categories and their regexes, if and when there's a match,
-		// the primitive's category is set to the category the regex beloings to.
-		for (PrimitiveCategory* cat : g_PrimitiveCategories)
-		{
-			for (RegexEntry& entry : cat->regexes)
-			{
-				switch (entry.type)
-				{
-					case EFilenameRegex:
-					{
-						// f-regex, check against filename
-						matched = entry.regex.exactMatch (prim.name);
-					} break;
-
-					case ETitleRegex:
-					{
-						// t-regex, check against title
-						matched = entry.regex.exactMatch (prim.title);
-					} break;
-				}
-
-				if (matched)
-				{
-					prim.category = cat;
-					break;
-				}
-			}
-
-			// Drop out if a category was decided on.
-			if (prim.category != null)
-				break;
-		}
-
-		// If there was a match, add the primitive to the category.
-		// Otherwise, add it to the list of unmatched primitives.
-		if (prim.category != null)
-			prim.category->prims << prim;
-		else
-			g_unmatched->prims << prim;
-	}
-
-	// Sort the categories. Note that we do this here because we need the existing
-	// order for regex matching.
-	qSort (g_PrimitiveCategories.begin(), g_PrimitiveCategories.end(),
-		[](PrimitiveCategory* const& a, PrimitiveCategory* const& b) -> bool
-		{
-			return a->name() < b->name();
-		});
-}
-
-// =============================================================================
-//
-void PrimitiveCategory::loadCategories()
-{
-	for (PrimitiveCategory* cat : g_PrimitiveCategories)
-		delete cat;
-
-	g_PrimitiveCategories.clear();
-	QString path = Config::DirectoryPath() + "primregexps.cfg";
-
-	if (not QFile::exists (path))
-		path = ":/data/primitive-categories.cfg";
-
-	QFile f (path);
-
-	if (not f.open (QIODevice::ReadOnly))
-	{
-		Critical (format (QObject::tr ("Failed to open primitive categories: %1"), f.errorString()));
-		return;
-	}
-
-	PrimitiveCategory* cat = null;
-
-	while (not f.atEnd())
-	{
-		QString line = f.readLine();
-		int colon;
-
-		if (line.endsWith ("\n"))
-			line.chop (1);
-
-		if (line.length() == 0 or line[0] == '#')
-			continue;
-
-		if ((colon = line.indexOf (":")) == -1)
-		{
-			if (cat and cat->isValidToInclude())
-				g_PrimitiveCategories << cat;
-
-			cat = new PrimitiveCategory (line);
-		}
-		elif (cat != null)
-		{
-			QString cmd = line.left (colon);
-			RegexType type = EFilenameRegex;
-
-			if (cmd == "f")
-				type = EFilenameRegex;
-			elif (cmd == "t")
-				type = ETitleRegex;
-			else
-			{
-				print (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line);
-				continue;
-			}
-
-			QRegExp regex (line.mid (colon + 1));
-			RegexEntry entry = { regex, type };
-			cat->regexes << entry;
-		}
-		else
-			print ("Warning: Rules given before the first category name");
-	}
-
-	if (cat->isValidToInclude())
-		g_PrimitiveCategories << cat;
-
-	// Add a category for unmatched primitives.
-	// Note: if this function is called the second time, g_unmatched has been
-	// deleted at the beginning of the function and is dangling at this point.
-	g_unmatched = new PrimitiveCategory (tr ("Other"));
-	g_PrimitiveCategories << g_unmatched;
-	f.close();
-}
-
-// =============================================================================
-//
-bool PrimitiveCategory::isValidToInclude()
-{
-	if (regexes.isEmpty())
-	{
-		print (tr ("Warning: category \"%1\" left without patterns"), name());
-		deleteLater();
-		return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-bool IsPrimitiveLoaderBusy()
-{
-	return g_activeScanner != null;
-}
-
-// =============================================================================
-//
-static double GetRadialPoint (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 * GetRadialPoint (i, divs, cos),
-			x1 = radius * GetRadialPoint (i + 1, divs, cos),
-			z0 = radius * GetRadialPoint (i, divs, sin),
-			z1 = radius * GetRadialPoint (i + 1, divs, sin);
-
-		lines << QLineF (QPointF (x0, z0), QPointF (x1, z1));
-	}
-}
-
-// =============================================================================
-//
-LDObjectList MakePrimitive (PrimitiveType type, int segs, int divs, int num)
-{
-	LDObjectList objs;
-	QList<int> condLineSegs;
-	QList<QLineF> circle;
-
-	MakeCircle (segs, divs, 1, circle);
-
-	for (int i = 0; i < segs; ++i)
-	{
-		double x0 = circle[i].x1(),
-				   x1 = circle[i].x2(),
-				   z0 = circle[i].y1(),
-				   z1 = circle[i].y2();
-
-		switch (type)
-		{
-			case Circle:
-			{
-				Vertex v0 (x0, 0.0f, z0),
-				  v1 (x1, 0.0f, z1);
-
-				LDLinePtr line (LDSpawn<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);
-
-				LDQuadPtr quad (LDSpawn<LDQuad> (v0, v1, v2, v3));
-				quad->setColor (MainColor());
-
-				if (type == Cylinder)
-					quad->invert();
-
-				objs << quad;
-
-				if (type == Cylinder or 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.
-				LDTrianglePtr seg (LDSpawn<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 and condLineSegs.size() != 0)
-		condLineSegs << segs;
-
-	for (int i : condLineSegs)
-	{
-		Vertex v0 (GetRadialPoint (i, divs, cos), 0.0f, GetRadialPoint (i, divs, sin)),
-		  v1,
-		  v2 (GetRadialPoint (i + 1, divs, cos), 0.0f, GetRadialPoint (i + 1, divs, sin)),
-		  v3 (GetRadialPoint (i - 1, divs, cos), 0.0f, GetRadialPoint (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.setX (v0.x() * num);
-			v0.setY (1.0);
-			v0.setZ (v0.z() * num);
-		}
-
-		LDCondLinePtr line = (LDSpawn<LDCondLine>());
-		line->setColor (EdgeColor());
-		line->setVertex (0, v0);
-		line->setVertex (1, v1);
-		line->setVertex (2, v2);
-		line->setVertex (3, v3);
-		objs << line;
-	}
-
-	return objs;
-}
-
-// =============================================================================
-//
-static QString PrimitiveTypeName (PrimitiveType type)
-{
-	// Not translated as primitives are in English.
-	return type == Circle   ? "Circle" :
-		   type == Cylinder ? "Cylinder" :
-		   type == Disc     ? "Disc" :
-		   type == DiscNeg  ? "Disc Negative" :
-		   type == Ring     ? "Ring" : "Cone";
-}
-
-// =============================================================================
-//
-QString MakeRadialFileName (PrimitiveType type, int segs, int divs, int num)
-{
-	int numer = segs,
-			denom = divs;
-
-	// Simplify the fractional part, but the denominator must be at least 4.
-	Simplify (numer, denom);
-
-	if (denom < 4)
-	{
-		const int factor = 4 / denom;
-		numer *= factor;
-		denom *= factor;
-	}
-
-	// Compose some general information: prefix, fraction, root, ring number
-	QString prefix = (divs == LowResolution) ? "" : format ("%1/", divs);
-	QString frac = format ("%1-%2", numer, denom);
-	QString root = g_radialNameRoots[type];
-	QString numstr = (type == Ring or type == Cone) ? format ("%1", num) : "";
-
-	// Truncate the root if necessary (7-16rin4.dat for instance).
-	// However, always keep the root at least 2 characters.
-	int extra = (frac.length() + numstr.length() + root.length()) - 8;
-	root.chop (Clamp (extra, 0, 2));
-
-	// Stick them all together and return the result.
-	return prefix + frac + root + numstr + ".dat";
-}
-
-// =============================================================================
-//
-LDDocumentPtr GeneratePrimitive (PrimitiveType type, int segs, int divs, int num)
-{
-	// Make the description
-	QString frac = QString::number ((float) segs / divs);
-	QString name = MakeRadialFileName (type, segs, divs, num);
-	QString descr;
-
-	// Ensure that there's decimals, even if they're 0.
-	if (frac.indexOf (".") == -1)
-		frac += ".0";
-
-	if (type == Ring or type == Cone)
-	{
-		QString spacing =
-			(num < 10) ? "  " :
-			(num < 100) ? " "  : "";
-
-		descr = format ("%1 %2%3 x %4", PrimitiveTypeName (type), spacing, num, frac);
-	}
-	else
-		descr = format ("%1 %2", PrimitiveTypeName (type), frac);
-
-	// Prepend "Hi-Res" if 48/ primitive.
-	if (divs == HighResolution)
-		descr.insert (0, "Hi-Res ");
-
-	LDDocumentPtr f = LDDocument::createNew();
-	f->setDefaultName (name);
-
-	QString author = APPNAME;
-	QString license = "";
-
-	if (not cfg::DefaultName.isEmpty())
-	{
-		license = PreferredLicenseText();
-		author = format ("%1 [%2]", cfg::DefaultName, cfg::DefaultUser);
-	}
-
-	LDObjectList objs;
-
-	objs << LDSpawn<LDComment> (descr)
-		 << LDSpawn<LDComment> (format ("Name: %1", name))
-		 << LDSpawn<LDComment> (format ("Author: %1", author))
-		 << LDSpawn<LDComment> (format ("!LDRAW_ORG Unofficial_%1Primitive",
-									  divs == HighResolution ?  "48_" : ""))
-		 << LDSpawn<LDComment> (license)
-		 << LDSpawn<LDEmpty>()
-		 << LDSpawn<LDBFC> (BFCStatement::CertifyCCW)
-		 << LDSpawn<LDEmpty>();
-
-	f->setImplicit (false);
-	f->history()->setIgnoring (false);
-	f->addObjects (objs);
-	f->addObjects (MakePrimitive (type, segs, divs, num));
-	f->addHistoryStep();
-	return f;
-}
-
-// =============================================================================
-//
-LDDocumentPtr GetPrimitive (PrimitiveType type, int segs, int divs, int num)
-{
-	QString name = MakeRadialFileName (type, segs, divs, num);
-	LDDocumentPtr 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 ? HighResolution : LowResolution);
-
-	// If the current value is 16 and we switch to hi-res, default the
-	// spinbox to 48.
-	if (on and ui->sb_segs->value() == LowResolution)
-		ui->sb_segs->setValue (HighResolution);
-}
-
-// =============================================================================
-//
-void MainWindow::actionMakePrimitive()
-{
-	PrimitivePrompt* dlg = new PrimitivePrompt (g_win);
-
-	if (not dlg->exec())
-		return;
-
-	int segs = dlg->ui->sb_segs->value();
-	int divs = dlg->ui->cb_hires->isChecked() ? HighResolution : LowResolution;
-	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;
-
-	LDDocumentPtr f = GeneratePrimitive (type, segs, divs, num);
-	f->setImplicit (false);
-	g_win->save (f, false);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/primitives.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,715 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDir>
+#include <QRegExp>
+#include <QFileDialog>
+#include "ldDocument.h"
+#include "mainWindow.h"
+#include "primitives.h"
+#include "ui_makeprim.h"
+#include "miscallenous.h"
+#include "colors.h"
+
+QList<PrimitiveCategory*> g_PrimitiveCategories;
+QList<Primitive> g_primitives;
+static PrimitiveScanner* g_activeScanner = null;
+PrimitiveCategory* g_unmatched = null;
+
+EXTERN_CFGENTRY (String, DefaultName)
+EXTERN_CFGENTRY (String, DefaultUser)
+EXTERN_CFGENTRY (Int, DefaultLicense)
+
+static const QStringList g_radialNameRoots =
+{
+	"edge",
+	"cyli",
+	"disc",
+	"ndis",
+	"ring",
+	"con"
+};
+
+PrimitiveScanner* ActivePrimitiveScanner()
+{
+	return g_activeScanner;
+}
+
+// =============================================================================
+//
+void LoadPrimitives()
+{
+	// Try to load prims.cfg
+	QFile conf (Config::FilePath ("prims.cfg"));
+
+	if (not conf.open (QIODevice::ReadOnly))
+	{
+		// No prims.cfg, build it
+		PrimitiveScanner::start();
+	}
+	else
+	{
+		while (not conf.atEnd())
+		{
+			QString line = conf.readLine();
+
+			if (line.endsWith ("\n"))
+				line.chop (1);
+
+			if (line.endsWith ("\r"))
+				line.chop (1);
+
+			int space = line.indexOf (" ");
+
+			if (space == -1)
+				continue;
+
+			Primitive info;
+			info.name = line.left (space);
+			info.title = line.mid (space + 1);
+			g_primitives << info;
+		}
+
+		PrimitiveCategory::populateCategories();
+		print ("%1 primitives loaded.\n", g_primitives.size());
+	}
+}
+
+// =============================================================================
+//
+static void GetRecursiveFilenames (QDir dir, QList<QString>& fnames)
+{
+	QFileInfoList flist = dir.entryInfoList (QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
+
+	for (const QFileInfo& info : flist)
+	{
+		if (info.isDir())
+			GetRecursiveFilenames (QDir (info.absoluteFilePath()), fnames);
+		else
+			fnames << info.absoluteFilePath();
+	}
+}
+
+// =============================================================================
+//
+PrimitiveScanner::PrimitiveScanner (QObject* parent) :
+	QObject (parent),
+	m_i (0)
+{
+	g_activeScanner = this;
+	QDir dir (LDPaths::prims());
+	assert (dir.exists());
+	m_baselen = dir.absolutePath().length();
+	GetRecursiveFilenames (dir, m_files);
+	emit starting (m_files.size());
+	print ("Scanning primitives...");
+}
+
+// =============================================================================
+//
+PrimitiveScanner::~PrimitiveScanner()
+{
+	g_activeScanner = null;
+}
+
+// =============================================================================
+//
+void PrimitiveScanner::work()
+{
+	int j = Min (m_i + 100, m_files.size());
+
+	for (; m_i < j; ++m_i)
+	{
+		QString fname = m_files[m_i];
+		QFile f (fname);
+
+		if (not f.open (QIODevice::ReadOnly))
+			continue;
+
+		Primitive info;
+		info.name = fname.mid (m_baselen + 1);  // make full path relative
+		info.name.replace ('/', '\\');  // use DOS backslashes, they're expected
+		info.category = null;
+		QByteArray titledata = f.readLine();
+
+		if (titledata != QByteArray())
+			info.title = QString::fromUtf8 (titledata);
+
+		info.title = info.title.simplified();
+
+		if (Q_LIKELY (info.title[0] == '0'))
+		{
+			info.title.remove (0, 1);  // remove 0
+			info.title = info.title.simplified();
+		}
+
+		m_prims << info;
+	}
+
+	if (m_i == m_files.size())
+	{
+		// Done with primitives, now save to a config file
+		QString path = Config::FilePath ("prims.cfg");
+		QFile conf (path);
+
+		if (not conf.open (QIODevice::WriteOnly | QIODevice::Text))
+			Critical (format ("Couldn't write primitive list %1: %2",
+				path, conf.errorString()));
+		else
+		{
+			for (Primitive& info : m_prims)
+				fprint (conf, "%1 %2\r\n", info.name, info.title);
+
+			conf.close();
+		}
+
+		g_primitives = m_prims;
+		PrimitiveCategory::populateCategories();
+		print ("%1 primitives scanned", g_primitives.size());
+		g_activeScanner = null;
+		emit workDone();
+		deleteLater();
+	}
+	else
+	{
+		// Defer to event loop, pick up the work later
+		emit update (m_i);
+		QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection);
+	}
+}
+
+// =============================================================================
+//
+void PrimitiveScanner::start()
+{
+	if (g_activeScanner)
+		return;
+
+	PrimitiveCategory::loadCategories();
+	PrimitiveScanner* scanner = new PrimitiveScanner;
+	scanner->work();
+}
+
+// =============================================================================
+//
+PrimitiveCategory::PrimitiveCategory (QString name, QObject* parent) :
+	QObject (parent),
+	m_name (name) {}
+
+// =============================================================================
+//
+void PrimitiveCategory::populateCategories()
+{
+	loadCategories();
+
+	for (PrimitiveCategory* cat : g_PrimitiveCategories)
+		cat->prims.clear();
+
+	for (Primitive& prim : g_primitives)
+	{
+		bool matched = false;
+		prim.category = null;
+
+		// Go over the categories and their regexes, if and when there's a match,
+		// the primitive's category is set to the category the regex beloings to.
+		for (PrimitiveCategory* cat : g_PrimitiveCategories)
+		{
+			for (RegexEntry& entry : cat->regexes)
+			{
+				switch (entry.type)
+				{
+					case EFilenameRegex:
+					{
+						// f-regex, check against filename
+						matched = entry.regex.exactMatch (prim.name);
+					} break;
+
+					case ETitleRegex:
+					{
+						// t-regex, check against title
+						matched = entry.regex.exactMatch (prim.title);
+					} break;
+				}
+
+				if (matched)
+				{
+					prim.category = cat;
+					break;
+				}
+			}
+
+			// Drop out if a category was decided on.
+			if (prim.category != null)
+				break;
+		}
+
+		// If there was a match, add the primitive to the category.
+		// Otherwise, add it to the list of unmatched primitives.
+		if (prim.category != null)
+			prim.category->prims << prim;
+		else
+			g_unmatched->prims << prim;
+	}
+
+	// Sort the categories. Note that we do this here because we need the existing
+	// order for regex matching.
+	qSort (g_PrimitiveCategories.begin(), g_PrimitiveCategories.end(),
+		[](PrimitiveCategory* const& a, PrimitiveCategory* const& b) -> bool
+		{
+			return a->name() < b->name();
+		});
+}
+
+// =============================================================================
+//
+void PrimitiveCategory::loadCategories()
+{
+	for (PrimitiveCategory* cat : g_PrimitiveCategories)
+		delete cat;
+
+	g_PrimitiveCategories.clear();
+	QString path = Config::DirectoryPath() + "primregexps.cfg";
+
+	if (not QFile::exists (path))
+		path = ":/data/primitive-categories.cfg";
+
+	QFile f (path);
+
+	if (not f.open (QIODevice::ReadOnly))
+	{
+		Critical (format (QObject::tr ("Failed to open primitive categories: %1"), f.errorString()));
+		return;
+	}
+
+	PrimitiveCategory* cat = null;
+
+	while (not f.atEnd())
+	{
+		QString line = f.readLine();
+		int colon;
+
+		if (line.endsWith ("\n"))
+			line.chop (1);
+
+		if (line.length() == 0 or line[0] == '#')
+			continue;
+
+		if ((colon = line.indexOf (":")) == -1)
+		{
+			if (cat and cat->isValidToInclude())
+				g_PrimitiveCategories << cat;
+
+			cat = new PrimitiveCategory (line);
+		}
+		elif (cat != null)
+		{
+			QString cmd = line.left (colon);
+			RegexType type = EFilenameRegex;
+
+			if (cmd == "f")
+				type = EFilenameRegex;
+			elif (cmd == "t")
+				type = ETitleRegex;
+			else
+			{
+				print (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line);
+				continue;
+			}
+
+			QRegExp regex (line.mid (colon + 1));
+			RegexEntry entry = { regex, type };
+			cat->regexes << entry;
+		}
+		else
+			print ("Warning: Rules given before the first category name");
+	}
+
+	if (cat->isValidToInclude())
+		g_PrimitiveCategories << cat;
+
+	// Add a category for unmatched primitives.
+	// Note: if this function is called the second time, g_unmatched has been
+	// deleted at the beginning of the function and is dangling at this point.
+	g_unmatched = new PrimitiveCategory (tr ("Other"));
+	g_PrimitiveCategories << g_unmatched;
+	f.close();
+}
+
+// =============================================================================
+//
+bool PrimitiveCategory::isValidToInclude()
+{
+	if (regexes.isEmpty())
+	{
+		print (tr ("Warning: category \"%1\" left without patterns"), name());
+		deleteLater();
+		return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+bool IsPrimitiveLoaderBusy()
+{
+	return g_activeScanner != null;
+}
+
+// =============================================================================
+//
+static double GetRadialPoint (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 * GetRadialPoint (i, divs, cos),
+			x1 = radius * GetRadialPoint (i + 1, divs, cos),
+			z0 = radius * GetRadialPoint (i, divs, sin),
+			z1 = radius * GetRadialPoint (i + 1, divs, sin);
+
+		lines << QLineF (QPointF (x0, z0), QPointF (x1, z1));
+	}
+}
+
+// =============================================================================
+//
+LDObjectList MakePrimitive (PrimitiveType type, int segs, int divs, int num)
+{
+	LDObjectList objs;
+	QList<int> condLineSegs;
+	QList<QLineF> circle;
+
+	MakeCircle (segs, divs, 1, circle);
+
+	for (int i = 0; i < segs; ++i)
+	{
+		double x0 = circle[i].x1(),
+				   x1 = circle[i].x2(),
+				   z0 = circle[i].y1(),
+				   z1 = circle[i].y2();
+
+		switch (type)
+		{
+			case Circle:
+			{
+				Vertex v0 (x0, 0.0f, z0),
+				  v1 (x1, 0.0f, z1);
+
+				LDLinePtr line (LDSpawn<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);
+
+				LDQuadPtr quad (LDSpawn<LDQuad> (v0, v1, v2, v3));
+				quad->setColor (MainColor());
+
+				if (type == Cylinder)
+					quad->invert();
+
+				objs << quad;
+
+				if (type == Cylinder or 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.
+				LDTrianglePtr seg (LDSpawn<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 and condLineSegs.size() != 0)
+		condLineSegs << segs;
+
+	for (int i : condLineSegs)
+	{
+		Vertex v0 (GetRadialPoint (i, divs, cos), 0.0f, GetRadialPoint (i, divs, sin)),
+		  v1,
+		  v2 (GetRadialPoint (i + 1, divs, cos), 0.0f, GetRadialPoint (i + 1, divs, sin)),
+		  v3 (GetRadialPoint (i - 1, divs, cos), 0.0f, GetRadialPoint (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.setX (v0.x() * num);
+			v0.setY (1.0);
+			v0.setZ (v0.z() * num);
+		}
+
+		LDCondLinePtr line = (LDSpawn<LDCondLine>());
+		line->setColor (EdgeColor());
+		line->setVertex (0, v0);
+		line->setVertex (1, v1);
+		line->setVertex (2, v2);
+		line->setVertex (3, v3);
+		objs << line;
+	}
+
+	return objs;
+}
+
+// =============================================================================
+//
+static QString PrimitiveTypeName (PrimitiveType type)
+{
+	// Not translated as primitives are in English.
+	return type == Circle   ? "Circle" :
+		   type == Cylinder ? "Cylinder" :
+		   type == Disc     ? "Disc" :
+		   type == DiscNeg  ? "Disc Negative" :
+		   type == Ring     ? "Ring" : "Cone";
+}
+
+// =============================================================================
+//
+QString MakeRadialFileName (PrimitiveType type, int segs, int divs, int num)
+{
+	int numer = segs,
+			denom = divs;
+
+	// Simplify the fractional part, but the denominator must be at least 4.
+	Simplify (numer, denom);
+
+	if (denom < 4)
+	{
+		const int factor = 4 / denom;
+		numer *= factor;
+		denom *= factor;
+	}
+
+	// Compose some general information: prefix, fraction, root, ring number
+	QString prefix = (divs == LowResolution) ? "" : format ("%1/", divs);
+	QString frac = format ("%1-%2", numer, denom);
+	QString root = g_radialNameRoots[type];
+	QString numstr = (type == Ring or type == Cone) ? format ("%1", num) : "";
+
+	// Truncate the root if necessary (7-16rin4.dat for instance).
+	// However, always keep the root at least 2 characters.
+	int extra = (frac.length() + numstr.length() + root.length()) - 8;
+	root.chop (Clamp (extra, 0, 2));
+
+	// Stick them all together and return the result.
+	return prefix + frac + root + numstr + ".dat";
+}
+
+// =============================================================================
+//
+LDDocumentPtr GeneratePrimitive (PrimitiveType type, int segs, int divs, int num)
+{
+	// Make the description
+	QString frac = QString::number ((float) segs / divs);
+	QString name = MakeRadialFileName (type, segs, divs, num);
+	QString descr;
+
+	// Ensure that there's decimals, even if they're 0.
+	if (frac.indexOf (".") == -1)
+		frac += ".0";
+
+	if (type == Ring or type == Cone)
+	{
+		QString spacing =
+			(num < 10) ? "  " :
+			(num < 100) ? " "  : "";
+
+		descr = format ("%1 %2%3 x %4", PrimitiveTypeName (type), spacing, num, frac);
+	}
+	else
+		descr = format ("%1 %2", PrimitiveTypeName (type), frac);
+
+	// Prepend "Hi-Res" if 48/ primitive.
+	if (divs == HighResolution)
+		descr.insert (0, "Hi-Res ");
+
+	LDDocumentPtr f = LDDocument::createNew();
+	f->setDefaultName (name);
+
+	QString author = APPNAME;
+	QString license = "";
+
+	if (not cfg::DefaultName.isEmpty())
+	{
+		license = PreferredLicenseText();
+		author = format ("%1 [%2]", cfg::DefaultName, cfg::DefaultUser);
+	}
+
+	LDObjectList objs;
+
+	objs << LDSpawn<LDComment> (descr)
+		 << LDSpawn<LDComment> (format ("Name: %1", name))
+		 << LDSpawn<LDComment> (format ("Author: %1", author))
+		 << LDSpawn<LDComment> (format ("!LDRAW_ORG Unofficial_%1Primitive",
+									  divs == HighResolution ?  "48_" : ""))
+		 << LDSpawn<LDComment> (license)
+		 << LDSpawn<LDEmpty>()
+		 << LDSpawn<LDBFC> (BFCStatement::CertifyCCW)
+		 << LDSpawn<LDEmpty>();
+
+	f->setImplicit (false);
+	f->history()->setIgnoring (false);
+	f->addObjects (objs);
+	f->addObjects (MakePrimitive (type, segs, divs, num));
+	f->addHistoryStep();
+	return f;
+}
+
+// =============================================================================
+//
+LDDocumentPtr GetPrimitive (PrimitiveType type, int segs, int divs, int num)
+{
+	QString name = MakeRadialFileName (type, segs, divs, num);
+	LDDocumentPtr 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 ? HighResolution : LowResolution);
+
+	// If the current value is 16 and we switch to hi-res, default the
+	// spinbox to 48.
+	if (on and ui->sb_segs->value() == LowResolution)
+		ui->sb_segs->setValue (HighResolution);
+}
+
+// =============================================================================
+//
+void MainWindow::actionMakePrimitive()
+{
+	PrimitivePrompt* dlg = new PrimitivePrompt (g_win);
+
+	if (not dlg->exec())
+		return;
+
+	int segs = dlg->ui->sb_segs->value();
+	int divs = dlg->ui->cb_hires->isChecked() ? HighResolution : LowResolution;
+	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;
+
+	LDDocumentPtr f = GeneratePrimitive (type, segs, divs, num);
+	f->setImplicit (false);
+	g_win->save (f, false);
+}
--- a/src/radioGroup.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-// I still find the radio group useful... find a way to use this in Designer.
-// I probably need to look into how to make Designer plugins.
-// TODO: try make this usable in Designer
-
-#include <QBoxLayout>
-#include <QRadioButton>
-#include <QButtonGroup>
-#include <QCheckBox>
-#include <map>
-
-#include "radioGroup.h"
-
-// =============================================================================
-//
-RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent)
-{
-	init (Qt::Vertical);
-}
-
-// =============================================================================
-//
-QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false)
-{
-	return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom;
-}
-
-// =============================================================================
-//
-bool RadioGroup::isChecked (int n) const
-{
-	return m_buttonGroup->checkedId() == n;
-}
-
-// =============================================================================
-//
-void RadioGroup::init (Qt::Orientation orient)
-{
-	m_vert = orient == Qt::Vertical;
-
-	m_buttonGroup = new QButtonGroup;
-	m_oldId = m_curId = 0;
-	m_coreLayout = null;
-
-	m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom);
-	setLayout (m_coreLayout);
-
-	// Init the first row with a break
-	rowBreak();
-
-	connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int)));
-	connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int)));
-}
-
-// =============================================================================
-//
-RadioGroup::RadioGroup (const QString& title, QList<char const*> entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) :
-		QGroupBox (title, parent),
-		m_defId (defaultId)
-{
-	init (orient);
-	m_oldId = m_defId;
-
-	for (const char* entry : entries)
-		addButton (entry);
-}
-
-// =============================================================================
-//
-void RadioGroup::rowBreak()
-{
-	QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight);
-	m_currentLayout = newLayout;
-	m_layouts << newLayout;
-
-	m_coreLayout->addLayout (newLayout);
-}
-
-// =============================================================================
-//
-void RadioGroup::addButton (const char* entry)
-{
-	QRadioButton* button = new QRadioButton (entry);
-	addButton (button);
-}
-
-// =============================================================================
-//
-void RadioGroup::addButton (QRadioButton* button)
-{
-	bool const selectThis = (m_curId == m_defId);
-
-	m_objects << button;
-	m_buttonGroup->addButton (button, m_curId++);
-	m_currentLayout->addWidget (button);
-
-	if (selectThis)
-		button->setChecked (true);
-}
-
-// =============================================================================
-//
-RadioGroup& RadioGroup::operator<< (QRadioButton* button)
-{
-	addButton (button);
-	return *this;
-}
-
-// =============================================================================
-//
-RadioGroup& RadioGroup::operator<< (const char* entry)
-{
-	addButton (entry);
-	return *this;
-}
-
-// =============================================================================
-//
-void RadioGroup::setCurrentRow (int row)
-{
-	m_currentLayout = m_layouts[row];
-}
-
-// =============================================================================
-//
-int RadioGroup::value() const
-{
-	return m_buttonGroup->checkedId();
-}
-
-// =============================================================================
-//
-void RadioGroup::setValue (int val)
-{
-	m_buttonGroup->button (val)->setChecked (true);
-}
-
-// =============================================================================
-//
-QRadioButton* RadioGroup::operator[] (int n) const
-{
-	return m_objects[n];
-}
-
-// =============================================================================
-//
-void RadioGroup::slot_buttonPressed (int btn)
-{
-	emit buttonPressed (btn);
-
-	m_oldId = m_buttonGroup->checkedId();
-}
-
-// =============================================================================
-//
-void RadioGroup::slot_buttonReleased (int btn)
-{
-	emit buttonReleased (btn);
-	int newid = m_buttonGroup->checkedId();
-
-	if (m_oldId != newid)
-		emit valueChanged (newid);
-}
-
-// =============================================================================
-//
-RadioGroup::Iterator RadioGroup::begin()
-{
-	return m_objects.begin();
-}
-
-// =============================================================================
-//
-RadioGroup::Iterator RadioGroup::end()
-{
-	return m_objects.end();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/radioGroup.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,194 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// I still find the radio group useful... find a way to use this in Designer.
+// I probably need to look into how to make Designer plugins.
+// TODO: try make this usable in Designer
+
+#include <QBoxLayout>
+#include <QRadioButton>
+#include <QButtonGroup>
+#include <QCheckBox>
+#include <map>
+
+#include "radioGroup.h"
+
+// =============================================================================
+//
+RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent)
+{
+	init (Qt::Vertical);
+}
+
+// =============================================================================
+//
+QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false)
+{
+	return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom;
+}
+
+// =============================================================================
+//
+bool RadioGroup::isChecked (int n) const
+{
+	return m_buttonGroup->checkedId() == n;
+}
+
+// =============================================================================
+//
+void RadioGroup::init (Qt::Orientation orient)
+{
+	m_vert = orient == Qt::Vertical;
+
+	m_buttonGroup = new QButtonGroup;
+	m_oldId = m_curId = 0;
+	m_coreLayout = null;
+
+	m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom);
+	setLayout (m_coreLayout);
+
+	// Init the first row with a break
+	rowBreak();
+
+	connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int)));
+	connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int)));
+}
+
+// =============================================================================
+//
+RadioGroup::RadioGroup (const QString& title, QList<char const*> entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) :
+		QGroupBox (title, parent),
+		m_defId (defaultId)
+{
+	init (orient);
+	m_oldId = m_defId;
+
+	for (const char* entry : entries)
+		addButton (entry);
+}
+
+// =============================================================================
+//
+void RadioGroup::rowBreak()
+{
+	QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight);
+	m_currentLayout = newLayout;
+	m_layouts << newLayout;
+
+	m_coreLayout->addLayout (newLayout);
+}
+
+// =============================================================================
+//
+void RadioGroup::addButton (const char* entry)
+{
+	QRadioButton* button = new QRadioButton (entry);
+	addButton (button);
+}
+
+// =============================================================================
+//
+void RadioGroup::addButton (QRadioButton* button)
+{
+	bool const selectThis = (m_curId == m_defId);
+
+	m_objects << button;
+	m_buttonGroup->addButton (button, m_curId++);
+	m_currentLayout->addWidget (button);
+
+	if (selectThis)
+		button->setChecked (true);
+}
+
+// =============================================================================
+//
+RadioGroup& RadioGroup::operator<< (QRadioButton* button)
+{
+	addButton (button);
+	return *this;
+}
+
+// =============================================================================
+//
+RadioGroup& RadioGroup::operator<< (const char* entry)
+{
+	addButton (entry);
+	return *this;
+}
+
+// =============================================================================
+//
+void RadioGroup::setCurrentRow (int row)
+{
+	m_currentLayout = m_layouts[row];
+}
+
+// =============================================================================
+//
+int RadioGroup::value() const
+{
+	return m_buttonGroup->checkedId();
+}
+
+// =============================================================================
+//
+void RadioGroup::setValue (int val)
+{
+	m_buttonGroup->button (val)->setChecked (true);
+}
+
+// =============================================================================
+//
+QRadioButton* RadioGroup::operator[] (int n) const
+{
+	return m_objects[n];
+}
+
+// =============================================================================
+//
+void RadioGroup::slot_buttonPressed (int btn)
+{
+	emit buttonPressed (btn);
+
+	m_oldId = m_buttonGroup->checkedId();
+}
+
+// =============================================================================
+//
+void RadioGroup::slot_buttonReleased (int btn)
+{
+	emit buttonReleased (btn);
+	int newid = m_buttonGroup->checkedId();
+
+	if (m_oldId != newid)
+		emit valueChanged (newid);
+}
+
+// =============================================================================
+//
+RadioGroup::Iterator RadioGroup::begin()
+{
+	return m_objects.begin();
+}
+
+// =============================================================================
+//
+RadioGroup::Iterator RadioGroup::end()
+{
+	return m_objects.end();
+}
--- a/src/ringFinder.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,224 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "ringFinder.h"
-#include "miscallenous.h"
-
-RingFinder g_RingFinder;
-
-RingFinder::RingFinder() {}
-
-// =============================================================================
-//
-bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution)
-{
-	// Don't recurse too deep.
-	if (m_stack >= 5)
-		return false;
-
-	// Find the scale and number of a ring between r1 and r0.
-	assert (r1 >= r0);
-	double scale = r1 - r0;
-	double num = r0 / scale;
-
-	// If the ring number is integral, we have found a fitting ring to r0 -> r1!
-	if (IsIntegral (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) and 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;
-}
-
-//
-// 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::findRings (double r0, double r1)
-{
-	m_solutions.clear();
-	Solution sol;
-
-	// If we're dealing with fractional radii, try upscale them into integral
-	// ones. This should yield in more reliable and more optimized results.
-	// For instance, using r0=1.5, r1=3.5 causes the algorithm to fail but
-	// r0=3, r1=7 (scaled up by 2) yields a 2-component solution. We can then
-	// downscale the radii back by dividing the scale fields of the solution
-	// components.
-	double scale = 1.0;
-
-	if (not IsZero (scale = r0 - floor (r0)) or not IsZero (scale = r1 - floor (r1)))
-	{
-		double r0f = r0 / scale;
-		double r1f = r1 / scale;
-
-		if (IsIntegral (r0f) and IsIntegral (r1f))
-		{
-			r0 = r0f;
-			r1 = r1f;
-		}
-		// If the numbers are both at most one-decimal fractions, we can use a scale of 10
-		elif (IsIntegral (r0 * 10) and IsIntegral (r1 * 10))
-		{
-			scale = 0.1;
-			r0 *= 10;
-			r1 *= 10;
-		}
-	}
-	else
-	{
-		scale = 1.0;
-	}
-
-	// Recurse in and try find solutions.
-	findRingsRecursor (r0, r1, sol);
-
-	// If we had upscaled our radii, downscale back now.
-	if (scale != 1.0)
-	{
-		for (Solution& sol : m_solutions)
-			sol.scaleComponents (scale);
-	}
-
-	// Compare the solutions and find the best one. The solution class has an operator>
-	// overload to compare two solutions.
-	m_bestSolution = null;
-
-	for (Solution const& sol : m_solutions)
-	{
-		if (m_bestSolution == null or sol.isSuperiorTo (m_bestSolution))
-			m_bestSolution = &sol;
-	}
-
-	return (m_bestSolution != null);
-}
-
-//
-// Compares this solution with @other and determines which
-// one is superior.
-//
-// A solution is considered superior if solution has less
-// components than the other one. If both solution have an
-// equal amount components, the solution with a lesser maximum
-// ring number is found superior, as such solutions should
-// yield less new primitives and cleaner definitions.
-//
-// The solution which is found superior to every other solution
-// will be the one returned by RingFinder::bestSolution().
-//
-bool RingFinder::Solution::isSuperiorTo (const Solution* other) const
-{
-	// If one solution has less components than the other one, it is definitely
-	// better.
-	if (getComponents().size() != other->getComponents().size())
-		return getComponents().size() < other->getComponents().size();
-
-	// Calculate the maximum ring number. Since the solutions have equal
-	// ring counts, the solutions with lesser maximum rings should result
-	// in cleaner code and less new primitives, right?
-	int maxA = 0,
-		maxB = 0;
-
-	for (int i = 0; i < getComponents().size(); ++i)
-	{
-		maxA = Max (getComponents()[i].num, maxA);
-		maxB = Max (other->getComponents()[i].num, maxB);
-	}
-
-	if (maxA != maxB)
-		return maxA < maxB;
-
-	// 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 RingFinder::Solution::scaleComponents (double scale)
-{
-	for (Component& cmp : m_components)
-		cmp.scale *= scale;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ringFinder.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,224 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ringFinder.h"
+#include "miscallenous.h"
+
+RingFinder g_RingFinder;
+
+RingFinder::RingFinder() {}
+
+// =============================================================================
+//
+bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution)
+{
+	// Don't recurse too deep.
+	if (m_stack >= 5)
+		return false;
+
+	// Find the scale and number of a ring between r1 and r0.
+	assert (r1 >= r0);
+	double scale = r1 - r0;
+	double num = r0 / scale;
+
+	// If the ring number is integral, we have found a fitting ring to r0 -> r1!
+	if (IsIntegral (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) and 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;
+}
+
+//
+// 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::findRings (double r0, double r1)
+{
+	m_solutions.clear();
+	Solution sol;
+
+	// If we're dealing with fractional radii, try upscale them into integral
+	// ones. This should yield in more reliable and more optimized results.
+	// For instance, using r0=1.5, r1=3.5 causes the algorithm to fail but
+	// r0=3, r1=7 (scaled up by 2) yields a 2-component solution. We can then
+	// downscale the radii back by dividing the scale fields of the solution
+	// components.
+	double scale = 1.0;
+
+	if (not IsZero (scale = r0 - floor (r0)) or not IsZero (scale = r1 - floor (r1)))
+	{
+		double r0f = r0 / scale;
+		double r1f = r1 / scale;
+
+		if (IsIntegral (r0f) and IsIntegral (r1f))
+		{
+			r0 = r0f;
+			r1 = r1f;
+		}
+		// If the numbers are both at most one-decimal fractions, we can use a scale of 10
+		elif (IsIntegral (r0 * 10) and IsIntegral (r1 * 10))
+		{
+			scale = 0.1;
+			r0 *= 10;
+			r1 *= 10;
+		}
+	}
+	else
+	{
+		scale = 1.0;
+	}
+
+	// Recurse in and try find solutions.
+	findRingsRecursor (r0, r1, sol);
+
+	// If we had upscaled our radii, downscale back now.
+	if (scale != 1.0)
+	{
+		for (Solution& sol : m_solutions)
+			sol.scaleComponents (scale);
+	}
+
+	// Compare the solutions and find the best one. The solution class has an operator>
+	// overload to compare two solutions.
+	m_bestSolution = null;
+
+	for (Solution const& sol : m_solutions)
+	{
+		if (m_bestSolution == null or sol.isSuperiorTo (m_bestSolution))
+			m_bestSolution = &sol;
+	}
+
+	return (m_bestSolution != null);
+}
+
+//
+// Compares this solution with @other and determines which
+// one is superior.
+//
+// A solution is considered superior if solution has less
+// components than the other one. If both solution have an
+// equal amount components, the solution with a lesser maximum
+// ring number is found superior, as such solutions should
+// yield less new primitives and cleaner definitions.
+//
+// The solution which is found superior to every other solution
+// will be the one returned by RingFinder::bestSolution().
+//
+bool RingFinder::Solution::isSuperiorTo (const Solution* other) const
+{
+	// If one solution has less components than the other one, it is definitely
+	// better.
+	if (getComponents().size() != other->getComponents().size())
+		return getComponents().size() < other->getComponents().size();
+
+	// Calculate the maximum ring number. Since the solutions have equal
+	// ring counts, the solutions with lesser maximum rings should result
+	// in cleaner code and less new primitives, right?
+	int maxA = 0,
+		maxB = 0;
+
+	for (int i = 0; i < getComponents().size(); ++i)
+	{
+		maxA = Max (getComponents()[i].num, maxA);
+		maxB = Max (other->getComponents()[i].num, maxB);
+	}
+
+	if (maxA != maxB)
+		return maxA < maxB;
+
+	// 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 RingFinder::Solution::scaleComponents (double scale)
+{
+	for (Component& cmp : m_components)
+		cmp.scale *= scale;
+}
--- a/src/version.cc	Sat Feb 21 20:23:34 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu 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/>.
- */
-
-// Note: only using stock C stuff in this file, keeping the STL/Qt stuff out
-// makes this simple file very fast to compile, which is nice since this file
-// must be compiled every time I commit something.
-
-#include <stdio.h>
-#include <string.h>
-#include <time.h>
-#include "version.h"
-#include "hginfo.h"
-
-// =============================================================================
-//
-const char* VersionString()
-{
-	static char result[64] = {'\0'};
-
-	if (result[0] == '\0')
-	{
-#if VERSION_PATCH == 0
-		sprintf (result, "%d.%d", VERSION_MAJOR, VERSION_MINOR);
-#else
-		sprintf (g_versionString, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
-#endif // VERSION_PATCH
-	}
-
-	return result;
-}
-
-// =============================================================================
-//
-const char* FullVersionString()
-{
-	static char result[256] = {'\0'};
-
-	if (result[0] == '\0')
-	{
-#if BUILD_ID != BUILD_RELEASE and defined (HG_NODE)
-		sprintf (result, "%s-" HG_NODE, VersionString());
-#else
-		sprintf (g_fullVersionString, "%s", VersionString());
-#endif
-	}
-
-	return result;
-}
-
-// =============================================================================
-//
-const char* CommitTimeString()
-{
-	static char result[256] = {'\0'};
-
-#ifdef HG_DATE_TIME
-	if (result[0] == '\0')
-	{
-		time_t timestamp = HG_DATE_TIME;
-		strftime (result, sizeof result, "%d %b %Y", localtime (&timestamp));
-	}
-#endif
-
-	return result;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/version.cpp	Tue Mar 03 16:55:36 2015 +0200
@@ -0,0 +1,80 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu 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/>.
+ */
+
+// Note: only using stock C stuff in this file, keeping the STL/Qt stuff out
+// makes this simple file very fast to compile, which is nice since this file
+// must be compiled every time I commit something.
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include "version.h"
+#include "hginfo.h"
+
+// =============================================================================
+//
+const char* VersionString()
+{
+	static char result[64] = {'\0'};
+
+	if (result[0] == '\0')
+	{
+#if VERSION_PATCH == 0
+		sprintf (result, "%d.%d", VERSION_MAJOR, VERSION_MINOR);
+#else
+		sprintf (g_versionString, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
+#endif // VERSION_PATCH
+	}
+
+	return result;
+}
+
+// =============================================================================
+//
+const char* FullVersionString()
+{
+	static char result[256] = {'\0'};
+
+	if (result[0] == '\0')
+	{
+#if BUILD_ID != BUILD_RELEASE and defined (HG_NODE)
+		sprintf (result, "%s-" HG_NODE, VersionString());
+#else
+		sprintf (g_fullVersionString, "%s", VersionString());
+#endif
+	}
+
+	return result;
+}
+
+// =============================================================================
+//
+const char* CommitTimeString()
+{
+	static char result[256] = {'\0'};
+
+#ifdef HG_DATE_TIME
+	if (result[0] == '\0')
+	{
+		time_t timestamp = HG_DATE_TIME;
+		strftime (result, sizeof result, "%d %b %Y", localtime (&timestamp));
+	}
+#endif
+
+	return result;
+}

mercurial