Moved source files to src/, removed zz_ prefix off dialog files.

Wed, 08 May 2013 15:19:06 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Wed, 08 May 2013 15:19:06 +0300
changeset 183
f1b8cb53d2a2
parent 182
9374fea8f77f
child 184
fae3bc9ce319

Moved source files to src/, removed zz_ prefix off dialog files.

bbox.cpp file | annotate | diff | comparison | revisions
bbox.h file | annotate | diff | comparison | revisions
colors.cpp file | annotate | diff | comparison | revisions
colors.h file | annotate | diff | comparison | revisions
common.h file | annotate | diff | comparison | revisions
config.cpp file | annotate | diff | comparison | revisions
config.h file | annotate | diff | comparison | revisions
extprogs.cpp file | annotate | diff | comparison | revisions
extprogs.h file | annotate | diff | comparison | revisions
file.cpp file | annotate | diff | comparison | revisions
file.h file | annotate | diff | comparison | revisions
gldraw.cpp file | annotate | diff | comparison | revisions
gldraw.h file | annotate | diff | comparison | revisions
gui.cpp file | annotate | diff | comparison | revisions
gui.h file | annotate | diff | comparison | revisions
gui_actions.cpp file | annotate | diff | comparison | revisions
gui_editactions.cpp file | annotate | diff | comparison | revisions
history.cpp file | annotate | diff | comparison | revisions
history.h file | annotate | diff | comparison | revisions
ldforge.pro file | annotate | diff | comparison | revisions
ldtypes.cpp file | annotate | diff | comparison | revisions
ldtypes.h file | annotate | diff | comparison | revisions
main.cpp file | annotate | diff | comparison | revisions
misc.cpp file | annotate | diff | comparison | revisions
misc.h file | annotate | diff | comparison | revisions
radiobox.cpp file | annotate | diff | comparison | revisions
radiobox.h file | annotate | diff | comparison | revisions
src/aboutDialog.cpp file | annotate | diff | comparison | revisions
src/aboutDialog.h file | annotate | diff | comparison | revisions
src/addObjectDialog.cpp file | annotate | diff | comparison | revisions
src/addObjectDialog.h file | annotate | diff | comparison | revisions
src/bbox.cpp file | annotate | diff | comparison | revisions
src/bbox.h file | annotate | diff | comparison | revisions
src/colorSelectDialog.cpp file | annotate | diff | comparison | revisions
src/colorSelectDialog.h file | annotate | diff | comparison | revisions
src/colors.cpp file | annotate | diff | comparison | revisions
src/colors.h file | annotate | diff | comparison | revisions
src/common.h file | annotate | diff | comparison | revisions
src/config.cpp file | annotate | diff | comparison | revisions
src/config.h file | annotate | diff | comparison | revisions
src/configDialog.cpp file | annotate | diff | comparison | revisions
src/configDialog.h file | annotate | diff | comparison | revisions
src/extprogs.cpp file | annotate | diff | comparison | revisions
src/extprogs.h file | annotate | diff | comparison | revisions
src/file.cpp file | annotate | diff | comparison | revisions
src/file.h file | annotate | diff | comparison | revisions
src/gldraw.cpp file | annotate | diff | comparison | revisions
src/gldraw.h file | annotate | diff | comparison | revisions
src/gui.cpp file | annotate | diff | comparison | revisions
src/gui.h file | annotate | diff | comparison | revisions
src/gui_actions.cpp file | annotate | diff | comparison | revisions
src/gui_editactions.cpp file | annotate | diff | comparison | revisions
src/history.cpp file | annotate | diff | comparison | revisions
src/history.h file | annotate | diff | comparison | revisions
src/historyDialog.cpp file | annotate | diff | comparison | revisions
src/historyDialog.h file | annotate | diff | comparison | revisions
src/ldrawPathDialog.cpp file | annotate | diff | comparison | revisions
src/ldrawPathDialog.h file | annotate | diff | comparison | revisions
src/ldtypes.cpp file | annotate | diff | comparison | revisions
src/ldtypes.h file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/misc.cpp file | annotate | diff | comparison | revisions
src/misc.h file | annotate | diff | comparison | revisions
src/newPartDialog.cpp file | annotate | diff | comparison | revisions
src/newPartDialog.h file | annotate | diff | comparison | revisions
src/radiobox.cpp file | annotate | diff | comparison | revisions
src/radiobox.h file | annotate | diff | comparison | revisions
src/setContentsDialog.cpp file | annotate | diff | comparison | revisions
src/setContentsDialog.h file | annotate | diff | comparison | revisions
src/str.cpp file | annotate | diff | comparison | revisions
src/str.h file | annotate | diff | comparison | revisions
src/types.cpp file | annotate | diff | comparison | revisions
src/types.h file | annotate | diff | comparison | revisions
str.cpp file | annotate | diff | comparison | revisions
str.h file | annotate | diff | comparison | revisions
types.cpp file | annotate | diff | comparison | revisions
types.h file | annotate | diff | comparison | revisions
zz_aboutDialog.cpp file | annotate | diff | comparison | revisions
zz_aboutDialog.h file | annotate | diff | comparison | revisions
zz_addObjectDialog.cpp file | annotate | diff | comparison | revisions
zz_addObjectDialog.h file | annotate | diff | comparison | revisions
zz_colorSelectDialog.cpp file | annotate | diff | comparison | revisions
zz_colorSelectDialog.h file | annotate | diff | comparison | revisions
zz_configDialog.cpp file | annotate | diff | comparison | revisions
zz_configDialog.h file | annotate | diff | comparison | revisions
zz_historyDialog.cpp file | annotate | diff | comparison | revisions
zz_historyDialog.h file | annotate | diff | comparison | revisions
zz_ldrawPathDialog.cpp file | annotate | diff | comparison | revisions
zz_ldrawPathDialog.h file | annotate | diff | comparison | revisions
zz_newPartDialog.cpp file | annotate | diff | comparison | revisions
zz_newPartDialog.h file | annotate | diff | comparison | revisions
zz_setContentsDialog.cpp file | annotate | diff | comparison | revisions
zz_setContentsDialog.h file | annotate | diff | comparison | revisions
--- a/bbox.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,167 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "common.h"
-#include "bbox.h"
-#include "ldtypes.h"
-#include "file.h"
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-bbox::bbox () {
-	reset ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void bbox::calculate () {
-	reset ();
-	
-	if (!g_curfile)
-		return;
-	
-	for (LDObject* obj : g_curfile->m_objs)
-		calcObject (obj);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void bbox::calcObject (LDObject* obj) {
-	switch (obj->getType ()) {
-	case LDObject::Line:
-		{
-			LDLine* line = static_cast<LDLine*> (obj);
-			for (short i = 0; i < 2; ++i)
-				calcVertex (line->vaCoords[i]);
-		}
-		break;
-	
-	case LDObject::Triangle:
-		{
-			LDTriangle* tri = static_cast<LDTriangle*> (obj);
-			for (short i = 0; i < 3; ++i)
-				calcVertex (tri->vaCoords[i]);
-		}
-		break;
-	
-	case LDObject::Quad:
-		{
-			LDQuad* quad = static_cast<LDQuad*> (obj);
-			for (short i = 0; i < 4; ++i)
-				calcVertex (quad->vaCoords[i]);
-		}
-		break;
-	
-	case LDObject::CondLine:
-		{
-			LDCondLine* line = static_cast<LDCondLine*> (obj);
-			for (short i = 0; i < 4; ++i)
-				calcVertex (line->vaCoords[i]);
-		}
-		break;
-	
-	case LDObject::Subfile:
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			vector<LDObject*> objs = ref->inlineContents (true, true);
-			
-			for (LDObject* obj : objs) {
-				calcObject (obj);
-				delete obj;
-			}
-		}
-		break;
-	
-	case LDObject::Radial:
-		{
-			LDRadial* rad = static_cast<LDRadial*> (obj);
-			vector<LDObject*> objs = rad->decompose (true);
-			
-			for (LDObject* obj : objs) {
-				calcObject (obj);
-				delete obj;
-			}
-		}
-		break;
-	
-	default:
-		break;
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void bbox::calcVertex (vertex v) {
-	for (const Axis ax : g_Axes) {
-		if (v[ax] < m_v0[ax])
-			m_v0[ax] = v[ax];
-		
-		if (v[ax] > m_v1[ax])
-			m_v1[ax] = v[ax];
-	}
-	
-	m_empty = false;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void bbox::reset () {
-	m_v0[X] = m_v0[Y] = m_v0[Z] = +0x7FFFFFFF;
-	m_v1[X] = m_v1[Y] = m_v1[Z] = -0x7FFFFFFF;
-	
-	m_empty = true;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-double bbox::size () const {
-	double fXScale = (m_v0[X] - m_v1[X]);
-	double fYScale = (m_v0[Y] - m_v1[Y]);
-	double fZScale = (m_v0[Z] - m_v1[Z]);
-	double fSize = fZScale;
-	
-	if (fXScale > fYScale) {
-		if (fXScale > fZScale)
-			fSize = fXScale;
-	} else if (fYScale > fZScale)
-		fSize = fYScale;
-	
-	if (abs (fSize) >= 2.0f)
-		return abs (fSize / 2);
-	
-	return 1.0f;
-}
-
-// =============================================================================
-vertex bbox::center () const {
-	return vertex (
-		(m_v0[X] + m_v1[X]) / 2,
-		(m_v0[Y] + m_v1[Y]) / 2,
-		(m_v0[Z] + m_v1[Z]) / 2);
-}
-
-// =============================================================================
-bool bbox::empty() const {
-	return m_empty;
-}
\ No newline at end of file
--- a/bbox.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef BBOX_H
-#define BBOX_H
-
-#include "common.h"
-#include "types.h"
-
-// =============================================================================
-// bbox
-//
-// The bounding box is the box that encompasses a given set of objects. The
-// global instance g_BBox is the bbox for the model we have open.
-// v0 is the minimum vertex, v1 is the maximum vertex.
-// =============================================================================
-class bbox {
-public:
-	bbox ();
-	void reset ();
-	void calculate ();
-	double size () const;
-	void calcObject (LDObject* obj);
-	void calcVertex (vertex v);
-	vertex center () const;
-	bool empty () const;
-	
-	bbox& operator<< (LDObject* obj) {
-		calcObject (obj);
-		return *this;
-	}
-	
-	bbox& operator<< (vertex& v) {
-		calcVertex (v);
-		return *this;
-	}
-	
-	const vertex& v0 () { return m_v0; }
-	const vertex& v1 () { return m_v1; }
-	
-private:
-	vertex m_v0, m_v1;
-	bool m_empty;
-};
-
-#endif // BBOX_H
\ No newline at end of file
--- a/colors.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "common.h"
-#include "colors.h"
-#include "file.h"
-#include "misc.h"
-#include <qcolor.h>
-
-static color* g_LDColors[MAX_COLORS];
-static bool g_bColorsInit = false;
-
-void initColors () {
-	if (g_bColorsInit)
-		return;
-	
-	logf ("%s: initializing color information.\n", __func__);
-	
-	color* col;
-	
-	// Always make sure there's 16 and 24 available. They're special like that.
-	col = new color;
-	col->zColorString = "#AAAAAA";
-	col->qColor = col->zColorString.chars ();
-	col->qEdge = Qt::black;
-	g_LDColors[maincolor] = col;
-	
-	col = new color;
-	col->zColorString = "#000000";
-	col->qEdge = col->qColor = Qt::black;
-	g_LDColors[edgecolor] = col;
-	
-	parseLDConfig ();
-	
-	g_bColorsInit = true;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-color* getColor (short dColorNum) {
-	// Check bounds
-	if (dColorNum < 0 || dColorNum >= MAX_COLORS)
-		return null;
-	
-	return g_LDColors[dColorNum];
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-static bool parseLDConfigTag (StringParser& pars, char const* sTag, str& zVal) {
-	short dPos;
-	if (!pars.findToken (dPos, sTag, 1))
-		return false;
-	
-	return pars.getToken (zVal, dPos + 1);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-short color::index () {
-	short idx = 0;
-	for (color* it : g_LDColors) {
-		if (it == this)
-			return idx;
-		idx++;
-	}
-	
-	return -1;
-}
-
-// =============================================================================
-uchar luma (QColor& col) {
-	return (0.2126f * col.red ()) +
-		(0.7152f * col.green ()) +
-		(0.0722f * col.blue ());
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void parseLDConfig () {
-	FILE* fp = openLDrawFile ("LDConfig.ldr", false);
-	
-	if (!fp)
-		return;
-	
-	// Even though LDConfig.ldr is technically an LDraw file, parsing it as one
-	// would be overkill by any standard.
-	char line[1024];
-	while (fgets (line, sizeof line, fp)) {
-		if (strlen (line) == 0 || line[0] != '0')
-			continue; // empty or illogical
-		
-		str zLine = line;
-		zLine.replace ("\n", "");
-		zLine.replace ("\r", "");
-		
-		StringParser pars (zLine, ' ');
-		short dCode = 0, dAlpha = 255;
-		str zName, zColor, zEdge, zValue;
-		
-		// Check 0 !COLOUR, parse the name
-		if (!pars.tokenCompare (0, "0") || !pars.tokenCompare (1, "!COLOUR") || !pars.getToken (zName, 2))
-			continue;
-		
-		// Replace underscores in the name with spaces for readability
-		zName.replace ("_", " ");
-		
-		// get the CODE tag
-		if (!parseLDConfigTag (pars, "CODE", zValue))
-			continue;
-		
-		// Ensure that the code is within range. must be within 0 - 512
-		dCode = atoi (zValue);
-		if (dCode < 0 || dCode >= 512)
-			continue;
-		
-		// Don't let LDConfig.ldr override the special colors 16 and 24. However,
-		// do take the name it gives for the color
-		if (dCode == maincolor || dCode == edgecolor) {
-			g_LDColors[dCode]->zName = zName;
-			continue;
-		}
-		
-		// VALUE tag
-		if (!parseLDConfigTag (pars, "VALUE", zColor))
-			continue;
-		
-		// EDGE tag
-		if (!parseLDConfigTag (pars, "EDGE", zEdge))
-			continue;
-		
-		// Ensure that our colors are correct
-		QColor qColor (zColor.chars()),
-			qEdge (zEdge.chars());
-		
-		if (!qColor.isValid () || !qEdge.isValid ())
-			continue;
-		
-		// Parse alpha if given.
-		if (parseLDConfigTag (pars, "ALPHA", zValue))
-			dAlpha = clamp<short> (atoi (zValue), 0, 255);
-		
-		color* col = new color;
-		col->zName = zName;
-		col->qColor = qColor;
-		col->qEdge = qEdge;
-		col->zColorString = zColor;
-		col->qColor.setAlpha (dAlpha);
-		
-		g_LDColors[dCode] = col;
-	}
-	
-	fclose (fp);
-}
\ No newline at end of file
--- a/colors.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef COLORS_H
-#define COLORS_H
-
-#include <qcolor.h>
-#include "common.h"
-
-#define MAX_COLORS 512
-
-class color {
-public:
-	str zName, zColorString;
-	QColor qColor, qEdge;
-	
-	short index ();
-};
-
-typedef struct {
-	const short dIndex;
-	const char* sName, *sColor;
-	const float fAlpha;
-} TemporaryColorMeta;
-
-void initColors ();
-void parseLDConfig ();
-uchar luma (QColor& col);
-
-// Safely gets a color with the given number or null if no such color.
-color* getColor (short dColorNum);
-
-#endif // COLORS_H
\ No newline at end of file
--- a/common.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,183 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-// =============================================================================
-// This file is included one way or another in every source file of LDForge.
-// Stuff defined and included here is universally included.
-
-#ifndef COMMON_H
-#define COMMON_H
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <vector>
-#include <stdint.h>
-#include <stdarg.h>
-#include "str.h"
-#include "config.h"
-#include "types.h"
-
-#define APPNAME "LDForge"
-
-#define VERSION_MAJOR 0
-#define VERSION_MAJOR_STR "0"
-#define VERSION_MINOR 1
-#define VERSION_MINOR_STR "1"
-
-// ============---
-// #define RELEASE
-
-// Version string identifier
-static const str versionString = fmt ("%d.%d", VERSION_MAJOR, VERSION_MINOR);
-
-#ifdef __GNUC__
-# define FORMAT_PRINTF(M,N) __attribute__ ((format (printf, M, N)))
-# define ATTR(N) __attribute__ ((N))
-#else
-# define FORMAT_PRINTF(M,N)
-# define ATTR(N)
-#endif // __GNUC__
-
-#ifdef WIN32
-#define DIRSLASH "\\"
-#else // WIN32
-#define DIRSLASH "/"
-#endif // WIN32
-
-#ifdef RELEASE
-#define NDEBUG // remove asserts
-#endif // RELEASE
-
-#ifdef null
-#undef null
-#endif // null
-
-// Null pointer
-static const std::nullptr_t null = nullptr;
-
-// Main and edge color identifiers
-static const short maincolor = 16;
-static const short edgecolor = 24;
-
-static const bool yup = true;
-static const bool nope = false;
-
-class ForgeWindow;
-class LDObject;
-class bbox;
-class OpenFile;
-class QApplication;
-
-// =============================================================================
-// Plural expression
-#define PLURAL(n) ((n != 1) ? "s" : "")
-
-// -----------------------------------------------------------------------------
-// Shortcut for formatting
-#define PERFORM_FORMAT(in, out) \
-	va_list v; \
-	va_start (v, in); \
-	char* out = vdynformat (in, v, 256); \
-	va_end (v);
-
-// -----------------------------------------------------------------------------
-// Shortcuts for stuffing vertices into printf-formatting.
-#define FMT_VERTEX "(%.3f, %.3f, %.3f)"
-#define FVERTEX(V) V.x, V.y, V.z
-
-// -----------------------------------------------------------------------------
-// Templated clamp
-template<class T> static inline T clamp (T a, T min, T max) {
-	return (a > max) ? max : (a < min) ? min : a;
-}
-
-// Templated minimum
-template<class T> static inline T min (T a, T b) {
-	return (a < b) ? a : b;
-}
-
-// Templated maximum
-template<class T> static inline T max (T a, T b) {
-	return (a > b) ? a : b;
-}
-
-// Templated absolute value
-template<class T> static inline T abs (T a) {
-	return (a >= 0) ? a : -a;
-}
-
-// Quick QString to const char* conversion
-static inline const char* qchars (QString qstr) {
-	return qstr.toStdString ().c_str ();
-}
-
-static const double pi = 3.14159265358979323846f;
-
-#ifdef IN_IDE_PARSER // KDevelop workaround
-// Current function name
-static const char* __func__ = "";
-#endif // IN_IDE_PARSER
-
-// -----------------------------------------------------------------------------
-enum LogType {
-	LOG_Normal,
-	LOG_Warning,
-	LOG_Error,
-	LOG_Dev,
-};
-
-// logf - universal access to the message log. Defined here so that I don't have
-// to include gui.h here and recompile everything every time that file changes.
-// logf is defined in main.cpp
-void logf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2);
-void logf (LogType type, const char* fmtstr, ...) FORMAT_PRINTF (2, 3);
-void warnf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2);
-void errf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2);
-
-#ifndef RELEASE
-void devf (const char* fmtstr, ...);
-#else
-# define devf(...)
-#endif // RELEASE
-
-// -----------------------------------------------------------------------------
-// Vertex at (0, 0, 0)
-extern const vertex g_origin;
-
-// -----------------------------------------------------------------------------
-// Pointer to the OpenFile which is currently being edited by the user.
-extern OpenFile* g_curfile;
-
-// -----------------------------------------------------------------------------
-// Pointer to the bounding box.
-extern bbox g_BBox;
-
-// -----------------------------------------------------------------------------
-// Vector of all currently opened files.
-extern vector<OpenFile*> g_loadedFiles;
-
-// -----------------------------------------------------------------------------
-// Pointer to the main application.
-extern const QApplication* g_app;
-
-// -----------------------------------------------------------------------------
-// Identity matrix
-extern const matrix<3> g_identity;
-
-#endif // COMMON_H
\ No newline at end of file
--- a/config.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,276 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <time.h>
-#include <QDir>
-#include "common.h"
-#include "str.h"
-#include "config.h"
-
-std::vector<config*> g_pConfigPointers;
-
-// =============================================================================
-const char* g_WeekdayNames[7] = {
-	"Sunday",
-	"Monday",
-	"Tuesday",
-	"Wednesday",
-	"Thursday",
-	"Friday",
-	"Saturday",
-};
-
-// =============================================================================
-static const char* g_MonthNames[12] = {
-	"Januray",
-	"February",
-	"March",
-	"April",
-	"May",
-	"June",
-	"July",
-	"August",
-	"September",
-	"October",
-	"November"
-	"December",
-};
-
-static const char* g_ConfigTypeNames[] = {
-	"None",
-	"Integer",
-	"String",
-	"Float",
-	"Boolean",
-	"Key sequence",
-};
-
-// =============================================================================
-// Load the configuration from file
-bool config::load () {
-	printf ("config::load: loading configuration file.\n");
-	
-	// Locale must be disabled for atof
-	setlocale (LC_NUMERIC, "C");
-	
-	FILE* fp = fopen (filepath().chars(), "r");
-	char linedata[MAX_INI_LINE];
-	char* line;
-	size_t ln = 0;
-	
-	if (!fp)
-		return false; // can't open for reading
-	
-	// Read the values.
-	while (fgets (linedata, MAX_INI_LINE, fp)) {
-		ln++;
-		line = linedata;
-		
-		while (*line != 0 && (*line <= 32 || *line >= 127))
-			line++; // Skip junk
-		
-		if (*line == '\0' || line[0] == '#')
-			continue; // Empty line or comment.
-		
-		// Find the equals sign.
-		char* equals = strchr (line, '=');
-		if (!equals) {
-			fprintf (stderr, "couldn't find `=` sign in entry `%s`\n", line);
-			continue;
-		}
-		
-		str entry = str (line).substr (0, equals - line);
-		
-		// Find the config entry for this.
-		config* cfg = null;
-		for (config* i : g_pConfigPointers)
-			if (entry == i->name)
-				cfg = i;
-		
-		if (!cfg) {
-			fprintf (stderr, "unknown config `%s`\n", entry.chars());
-			continue;
-		}
-		
-		str valstring = str (line).substr (equals - line + 1, -1);
-		
-		// Trim the crap off the end
-		while (~valstring) {
-			char c = valstring[~valstring - 1];
-			if (c <= 32 || c >= 127)
-				valstring -= 1;
-			else
-				break;
-		}
-		
-		switch (cfg->getType()) {
-		case CONFIG_int:
-			static_cast<intconfig*> (cfg)->value = atoi (valstring.chars());
-			break;
-		
-		case CONFIG_str:
-			static_cast<strconfig*> (cfg)->value = valstring;
-			break;
-		
-		case CONFIG_float:
-			static_cast<floatconfig*> (cfg)->value = atof (valstring.chars());
-			break;
-		
-		case CONFIG_bool:
-		{
-			bool& val = static_cast<boolconfig*> (cfg)->value;
-			
-			if (+valstring == "TRUE" || valstring == "1")
-				val = true;
-			else if (+valstring == "FALSE" || valstring == "0")
-				val = false;
-			break;
-		}
-		
-		case CONFIG_keyseq:
-			static_cast<keyseqconfig*> (cfg)->value = keyseq::fromString (valstring.chars ());
-			break;
-		
-		default:
-			break;
-		}
-	}
-	
-	fclose (fp);
-	return true;
-}
-
-// =============================================================================
-// Write a given formatted string to the given file stream
-static size_t writef (FILE* fp, const char* fmt, ...) {
-	va_list va;
-	
-	va_start (va, fmt);
-	char* buf = vdynformat (fmt, va, 256);
-	va_end (va);
-	
-	size_t len = fwrite (buf, 1, strlen (buf), fp);
-	delete[] buf;
-	
-	return len;
-}
-
-// =============================================================================
-// Save the configuration to disk
-bool config::save () {
-	// The function will write floats, disable the locale now so that they
-	// are written properly.
-	setlocale (LC_NUMERIC, "C");
-	
-	// If the directory doesn't exist, create it now.
-	if (QDir (dirpath ()).exists () == nope) {
-		fprintf (stderr, "Creating config path %s...\n", dirpath().chars());
-		if (!QDir ().mkpath (dirpath().chars())) {
-			logf (LOG_Warning, "Failed to create the directory. Configuration cannot be saved!\n");
-			return false; // Couldn't create directory
-		}
-	}
-	
-	FILE* fp = fopen (filepath().chars(), "w");
-	printf ("writing cfg to %s\n", filepath().chars());
-	
-	if (!fp) {
-		printf ("Couldn't open %s for writing\n", filepath().chars());
-		return false;
-	}
-	
-	const time_t curtime = time (NULL);
-	const struct tm* timeinfo = localtime (&curtime);
-	const char* daysuffix =
-		(timeinfo->tm_mday % 10 == 1) ? "st" :
-		(timeinfo->tm_mday % 10 == 2) ? "nd" :
-		(timeinfo->tm_mday % 10 == 3) ? "rd" : "th";
-	
-	writef (fp, "# Configuration file for " APPNAME "\n");
-	writef (fp, "# Written on %s, %s %d%s %d %.2d:%.2d:%.2d\n",
-		g_WeekdayNames[timeinfo->tm_wday], g_MonthNames[timeinfo->tm_mon],
-		timeinfo->tm_mday, daysuffix, timeinfo->tm_year + 1900,
-		timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
-	
-	for (config* cfg : g_pConfigPointers) {
-		str valstring;
-		switch (cfg->getType()) {
-		case CONFIG_int:
-			valstring.format ("%d", static_cast<intconfig*> (cfg)->value);
-			break;
-		case CONFIG_str:
-			valstring = static_cast<strconfig*> (cfg)->value;
-			break;
-		case CONFIG_float:
-			valstring.format ("%f", static_cast<floatconfig*> (cfg)->value);
-			
-			// Trim any trailing zeros
-			if (valstring.first (".") != -1) {
-				while (valstring[~valstring - 1] == '0')
-					valstring -= 1;
-				
-				// But don't trim the only one out...
-				if (valstring[~valstring - 1] == '.')
-					valstring += '0';
-			}
-			
-			break;
-		case CONFIG_bool:
-			valstring = (static_cast<boolconfig*> (cfg)->value) ? "true" : "false";
-			break;
-		case CONFIG_keyseq:
-			valstring = static_cast<keyseqconfig*> (cfg)->value.toString ();
-			break;
-		default:
-			break;
-		}
-		
-		const char* sDefault = (cfg->getType() != CONFIG_keyseq) ? cfg->defaultstring :
-			static_cast<keyseqconfig*> (cfg)->defval.toString ().toUtf8 ().constData ();
-		
-		// Write the entry now.
-		writef (fp, "\n# [%s] default: %s\n", g_ConfigTypeNames[cfg->getType()], sDefault);
-		writef (fp, "%s=%s\n", cfg->name, valstring.chars());
-	}
-	
-	fclose (fp);
-	return true;
-}
-
-// =============================================================================
-void config::reset () {
-	for (size_t i = 0; i < NUM_CONFIG; i++)
-		g_pConfigPointers[i]->resetValue ();
-}
-
-// =============================================================================
-str config::filepath () {
-	str path;
-	path.format ("%s%s.cfg", dirpath ().chars (),
-		str (APPNAME).tolower ().chars ());
-	return path;
-}
-
-// =============================================================================
-str config::dirpath () {
-	return fmt ("%s/.%s/", qchars (QDir::homePath ()),
-		str (APPNAME).tolower ().chars ());
-}
\ No newline at end of file
--- a/config.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,227 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef CONFIG_H
-#define CONFIG_H
-
-#include "common.h"
-#include "str.h"
-
-// =============================================================================
-#include <QString>
-#include <qkeysequence.h>
-
-#define MAX_INI_LINE 512
-#define NUM_CONFIG (g_pConfigPointers.size ())
-
-#define cfg(T, NAME, DEFAULT) \
-	T##config NAME (DEFAULT, #NAME, #T, #DEFAULT)
-
-#define extern_cfg(T, NAME) \
-	extern T##config NAME
-
-// =============================================================================
-enum configtype_e {
-	CONFIG_none,
-	CONFIG_int,
-	CONFIG_str,
-	CONFIG_float,
-	CONFIG_bool,
-	CONFIG_keyseq,
-};
-
-// =========================================================
-class config {
-public:
-	const char* name, *typestring, *defaultstring;
-	
-	virtual configtype_e getType () {
-		return CONFIG_none;
-	}
-	
-	virtual void resetValue () {}
-	
-	// ------------------------------------------
-	static bool load ();
-	static bool save ();
-	static void reset ();
-	static str dirpath ();
-	static str filepath ();
-};
-
-extern std::vector<config*> g_pConfigPointers;
-
-// =============================================================================
-#define DEFINE_UNARY_OPERATOR(T, OP) \
-	T operator OP () { \
-		return (OP value); \
-	} \
-
-#define DEFINE_BINARY_OPERATOR(T, OP) \
-	T operator OP (const T other) { \
-		return (value OP other); \
-	} \
-
-#define DEFINE_ASSIGN_OPERATOR(T, OP) \
-	T& operator OP (const T other) { \
-		return (value OP other); \
-	} \
-
-#define DEFINE_COMPARE_OPERATOR(T, OP) \
-	bool operator OP (const T other) { \
-		return (value OP other); \
-	} \
-
-#define DEFINE_CAST_OPERATOR(T) \
-	operator T () { \
-		return (T) value; \
-	} \
-
-#define DEFINE_ALL_COMPARE_OPERATORS(T) \
-	DEFINE_COMPARE_OPERATOR (T, ==) \
-	DEFINE_COMPARE_OPERATOR (T, !=) \
-	DEFINE_COMPARE_OPERATOR (T, >) \
-	DEFINE_COMPARE_OPERATOR (T, <) \
-	DEFINE_COMPARE_OPERATOR (T, >=) \
-	DEFINE_COMPARE_OPERATOR (T, <=) \
-
-#define DEFINE_INCREMENT_OPERATORS(T) \
-	T operator++ () {return ++value;} \
-	T operator++ (int) {return value++;} \
-	T operator-- () {return --value;} \
-	T operator-- (int) {return value--;}
-
-#define CONFIGTYPE(T) \
-class T##config : public config
-
-#define IMPLEMENT_CONFIG(T) \
-	T value, defval; \
-	\
-	T##config (T _defval, const char* _name, const char* _typestring, \
-		const char* _defaultstring) \
-	{ \
-		value = defval = _defval; \
-		name = _name; \
-		typestring = _typestring; \
-		defaultstring = _defaultstring; \
-		g_pConfigPointers.push_back (this); \
-	} \
-	operator T () { \
-		return value; \
-	} \
-	configtype_e getType () { \
-		return CONFIG_##T; \
-	} \
-	virtual void resetValue () { \
-		value = defval; \
-	}
-
-// =============================================================================
-CONFIGTYPE (int) {
-public:
-	IMPLEMENT_CONFIG (int)
-	
-	// Int-specific operators
-	DEFINE_ALL_COMPARE_OPERATORS (int)
-	DEFINE_INCREMENT_OPERATORS (int)
-	DEFINE_BINARY_OPERATOR (int, +)
-	DEFINE_BINARY_OPERATOR (int, -)
-	DEFINE_BINARY_OPERATOR (int, *)
-	DEFINE_BINARY_OPERATOR (int, /)
-	DEFINE_BINARY_OPERATOR (int, %)
-	DEFINE_BINARY_OPERATOR (int, ^)
-	DEFINE_BINARY_OPERATOR (int, |)
-	DEFINE_BINARY_OPERATOR (int, &)
-	DEFINE_BINARY_OPERATOR (int, >>)
-	DEFINE_BINARY_OPERATOR (int, <<)
-	DEFINE_UNARY_OPERATOR (int, !)
-	DEFINE_UNARY_OPERATOR (int, ~)
-	DEFINE_UNARY_OPERATOR (int, -)
-	DEFINE_UNARY_OPERATOR (int, +)
-	DEFINE_ASSIGN_OPERATOR (int, =)
-	DEFINE_ASSIGN_OPERATOR (int, +=)
-	DEFINE_ASSIGN_OPERATOR (int, -=)
-	DEFINE_ASSIGN_OPERATOR (int, *=)
-	DEFINE_ASSIGN_OPERATOR (int, /=)
-	DEFINE_ASSIGN_OPERATOR (int, %=)
-	DEFINE_ASSIGN_OPERATOR (int, >>=)
-	DEFINE_ASSIGN_OPERATOR (int, <<=)
-};
-
-// =============================================================================
-CONFIGTYPE (str) {
-public:
-	IMPLEMENT_CONFIG (str)
-	
-	DEFINE_ALL_COMPARE_OPERATORS (str)
-	DEFINE_BINARY_OPERATOR (str, -)
-	DEFINE_BINARY_OPERATOR (str, *)
-	DEFINE_UNARY_OPERATOR (str, !)
-	DEFINE_ASSIGN_OPERATOR (str, =)
-	DEFINE_ASSIGN_OPERATOR (str, +=)
-	DEFINE_ASSIGN_OPERATOR (str, -=)
-	DEFINE_ASSIGN_OPERATOR (str, *=)
-	DEFINE_CAST_OPERATOR (char*)
-	
-	char operator[] (size_t n) {
-		return value[n];
-	}
-	
-#ifdef CONFIG_WITH_QT
-	operator QString () {
-		return QString (value.chars());
-	}
-#endif // CONFIG_WITH_QT
-};
-
-// =============================================================================
-CONFIGTYPE (float) {
-public:
-	IMPLEMENT_CONFIG (float)
-	
-	DEFINE_ALL_COMPARE_OPERATORS (float)
-	DEFINE_INCREMENT_OPERATORS (float)
-	DEFINE_BINARY_OPERATOR (float, +)
-	DEFINE_BINARY_OPERATOR (float, -)
-	DEFINE_BINARY_OPERATOR (float, *)
-	DEFINE_UNARY_OPERATOR (float, !)
-	DEFINE_ASSIGN_OPERATOR (float, =)
-	DEFINE_ASSIGN_OPERATOR (float, +=)
-	DEFINE_ASSIGN_OPERATOR (float, -=)
-	DEFINE_ASSIGN_OPERATOR (float, *=)
-};
-
-// =============================================================================
-CONFIGTYPE (bool) {
-public:
-	IMPLEMENT_CONFIG (bool)
-	DEFINE_ALL_COMPARE_OPERATORS (bool)
-	DEFINE_ASSIGN_OPERATOR (bool, =)
-};
-
-// =============================================================================
-typedef QKeySequence keyseq;
-
-CONFIGTYPE (keyseq) {
-public:
-	IMPLEMENT_CONFIG (keyseq)
-	DEFINE_ALL_COMPARE_OPERATORS (keyseq)
-	DEFINE_ASSIGN_OPERATOR (keyseq, =)
-};
-
-#endif // CONFIG_H
\ No newline at end of file
--- a/extprogs.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,461 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <qprocess.h>
-#include <qtemporaryfile.h>
-#include <qeventloop.h>
-#include <qdialog.h>
-#include <qdialogbuttonbox.h>
-#include <qspinbox.h>
-#include <qcheckbox.h>
-#include <qcombobox.h>
-#include "common.h"
-#include "config.h"
-#include "misc.h"
-#include "extprogs.h"
-#include "gui.h"
-#include "file.h"
-#include "radiobox.h"
-#include "history.h"
-
-// =============================================================================
-cfg (str, prog_isecalc, "");
-cfg (str, prog_intersector, "");
-cfg (str, prog_coverer, "");
-cfg (str, prog_ytruder, "");
-cfg (str, prog_datheader, "");
-cfg (str, prog_rectifier, "");
-
-const char* g_extProgNames[] = {
-	"Isecalc",
-	"Intersector",
-	"Coverer",
-	"Ytruder",
-	"Rectifier",
-	"DATHeader",
-};
-
-// =============================================================================
-static bool checkProgPath (str path, const extprog prog) {
-	if (~path)
-		return true;
-	
-	const char* name = g_extProgNames[prog];
-	
-	critical (fmt ("Couldn't run %s as no path has "
-		"been defined for it. Use the configuration dialog's External Programs "
-		"tab to define a path for %s.", name, name));
-	return false;
-}
-
-// =============================================================================
-static void processError (const extprog prog, QProcess& proc) {
-	const char* name = g_extProgNames[prog];
-	str errmsg;
-	
-	switch (proc.error ()) {
-	case QProcess::FailedToStart:
-		errmsg = fmt ("Failed to launch %s. Check that you have set the proper path "
-			"to %s and that you have the proper permissions to launch it.", name, name);
-		break;
-	
-	case QProcess::Crashed:
-		errmsg = fmt ("%s crashed.", name);
-		break;
-	
-	case QProcess::WriteError:
-	case QProcess::ReadError:
-		errmsg = fmt ("I/O error while interacting with %s.", name);
-		break;
-	
-	case QProcess::UnknownError:
-		errmsg = fmt ("Unknown error occurred while executing %s.", name);
-		break;
-	
-	case QProcess::Timedout:
-		errmsg = fmt ("%s timed out.", name);
-		break;
-	}
-	
-	critical (errmsg);
-}
-
-// =============================================================================
-static bool mkTempFile (QTemporaryFile& tmp, str& fname) {
-	if (!tmp.open ())
-		return false;
-	
-	fname = tmp.fileName ();
-	tmp.close ();
-	return true;
-}
-
-// =============================================================================
-void writeObjects (std::vector<LDObject*>& objects, str fname) {
-	// Write the input file
-	FILE* fp = fopen (fname, "w");
-	if (!fp) {
-		critical (fmt ("Couldn't open temporary file %s for writing.\n", fname.chars ()));
-		return;
-	}
-	
-	for (LDObject* obj : objects) {
-		str line = fmt ("%s\r\n", obj->getContents ().chars ());
-		fwrite (line.chars(), 1, ~line, fp);
-	}
-	
-#ifndef RELEASE
-	ushort idx = rand ();
-	printf ("%s -> debug_%u\n", fname.chars (), idx);
-	QFile::copy (fname.chars (), fmt ("debug_%u", idx));
-#endif // RELEASE
-	
-	fclose (fp);
-}
-
-// =============================================================================
-void writeSelection (str fname) {
-	writeObjects (g_win->sel (), fname);
-}
-
-// =============================================================================
-void writeColorGroup (const short colnum, str fname) {
-	std::vector<LDObject*> objects;
-	for (LDObject*& obj : g_curfile->m_objs) {
-		if (obj->isColored () == false || obj->dColor != colnum)
-			continue;
-		
-		objects.push_back (obj);
-	}
-	
-	writeObjects (objects, fname);
-}
-
-// =============================================================================
-void runUtilityProcess (extprog prog, str path, QString argvstr) {
-	QTemporaryFile input, output;
-	str inputname, outputname;
-	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
-	
-	printf ("cmdline: %s %s\n", path.chars (), qchars (argvstr));
-	
-	if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname))
-		return;
-	
-	QProcess proc;
-	
-	// Init stdin
-	FILE* stdinfp = fopen (inputname, "w");
-	
-	// Begin!
-	proc.setStandardInputFile (inputname);
-	proc.start (path, argv);
-	
-	// Write an enter - one is expected
-	char enter[2] = "\n";
-	enter[1] = '\0';
-	fwrite (enter, 1, sizeof enter, stdinfp);
-	fflush (stdinfp);
-	
-	// Wait while it runs
-	proc.waitForFinished ();
-	
-#ifndef RELASE
-	printf ("%s", qchars (QString (proc.readAllStandardOutput ())));
-#endif // RELEASE
-	
-	if (proc.exitStatus () == QProcess::CrashExit) {
-		processError (prog, proc);
-		return;
-	}
-}
-
-// ========================================================================================================================================
-static void insertOutput (str fname, bool replace, vector<short> colorsToReplace) {
-#ifndef RELEASE
-	QFile::copy (fname, "./debug_lastOutput");
-#endif // RELEASE
-	
-	// Read the output file
-	FILE* fp = fopen (fname, "r");
-	if (!fp) {
-		critical (fmt ("Couldn't open temporary file %s for reading.\n", fname.chars ()));
-		return;
-	}
-	
-	ComboHistory* cmb = new ComboHistory ({});
-	std::vector<LDObject*> objs = loadFileContents (fp, null),
-		copies;
-	std::vector<ulong> indices;
-	
-	// If we replace the objects, delete the selection now.
-	if (replace)
-		*cmb << g_win->deleteSelection ();
-	
-	for (const short colnum : colorsToReplace)
-		*cmb << g_win->deleteByColor (colnum);
-	
-	// Insert the new objects
-	g_win->sel ().clear ();
-	for (LDObject* obj : objs) {
-		if (!obj->isSchemantic ()) {
-			delete obj;
-			continue;
-		}
-		
-		ulong idx = g_curfile->addObject (obj);
-		indices.push_back (idx);
-		copies.push_back (obj->clone ());
-		g_win->sel ().push_back (obj);
-	}
-	
-	if (indices.size() > 0)
-		*cmb << new AddHistory ({indices, copies});
-	
-	if (cmb->paEntries.size () > 0)
-		History::addEntry (cmb);
-	else
-		delete cmb;
-	
-	fclose (fp);
-	g_win->refresh ();
-}
-
-QDialogButtonBox* makeButtonBox (QDialog& dlg) {
-	QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-	QWidget::connect (bbx_buttons, SIGNAL (accepted ()), &dlg, SLOT (accept ()));
-	QWidget::connect (bbx_buttons, SIGNAL (rejected ()), &dlg, SLOT (reject ()));
-	return bbx_buttons;
-}
-
-// =============================================================================
-// Interface for Ytruder
-MAKE_ACTION (ytruder, "Ytruder", "ytruder", "Extrude selected lines to a given plane", KEY (F4)) {
-	setlocale (LC_ALL, "C");
-	
-	if (!checkProgPath (prog_ytruder, Ytruder))
-		return;
-	
-	QDialog dlg;
-	
-	RadioBox* rb_mode = new RadioBox ("Extrusion mode", {"Distance", "Symmetry", "Projection", "Radial"}, 0, Qt::Horizontal);
-	RadioBox* rb_axis = new RadioBox ("Axis", {"X", "Y", "Z"}, 0, Qt::Horizontal);
-	LabeledWidget<QDoubleSpinBox>* dsb_depth = new LabeledWidget<QDoubleSpinBox> ("Plane depth"),
-		*dsb_condAngle = new LabeledWidget<QDoubleSpinBox> ("Conditional line threshold");
-	
-	rb_axis->setValue (Y);
-	dsb_depth->w ()->setMinimum (-10000.0);
-	dsb_depth->w ()->setMaximum (10000.0);
-	dsb_depth->w ()->setDecimals (3);
-	dsb_condAngle->w ()->setValue (30.0f);
-	
-	QVBoxLayout* layout = new QVBoxLayout (&dlg);
-	layout->addWidget (rb_mode);
-	layout->addWidget (rb_axis);
-	layout->addWidget (dsb_depth);
-	layout->addWidget (dsb_condAngle);
-	layout->addWidget (makeButtonBox (dlg));
-	
-	dlg.setWindowIcon (getIcon ("extrude"));
-	
-	if (!dlg.exec ())
-		return;
-	
-	// Read the user's choices
-	const enum modetype { Distance, Symmetry, Projection, Radial } mode = (modetype) rb_mode->value ();
-	const Axis axis = (Axis) rb_axis->value ();
-	const double depth = dsb_depth->w ()->value (),
-		condAngle = dsb_condAngle->w ()->value ();
-	
-	QTemporaryFile indat, outdat;
-	str inDATName, outDATName;
-	
-	// Make temp files for the input and output files
-	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
-		return;
-	
-	// Compose the command-line arguments
-	str argv = fmt ("%s %s %f -a %f %s %s",
-		(axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
-		(mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
-		depth, condAngle, inDATName.chars (), outDATName.chars ());
-	
-	writeSelection (inDATName);
-	runUtilityProcess (Ytruder, prog_ytruder, argv);
-	insertOutput (outDATName, false, {});
-}
-
-// ========================================================================================================================================
-// Rectifier interface
-MAKE_ACTION (rectifier, "Rectifier", "rectifier", "Optimizes quads into rect primitives.", KEY (F8)) {
-	setlocale (LC_ALL, "C");
-	
-	if (!checkProgPath (prog_rectifier, Rectifier))
-		return;
-	
-	QDialog dlg;
-	QCheckBox* cb_condense = new QCheckBox ("Condense triangles to quads"),
-		*cb_subst = new QCheckBox ("Substitute rect primitives"),
-		*cb_condlineCheck = new QCheckBox ("Don't replace quads with adj. condlines"),
-		*cb_colorize = new QCheckBox ("Colorize resulting objects");
-	LabeledWidget<QDoubleSpinBox>* dsb_coplthres = new LabeledWidget<QDoubleSpinBox> ("Coplanarity threshold");
-	
-	dsb_coplthres->w ()->setMinimum (0.0f);
-	dsb_coplthres->w ()->setMaximum (360.0f);
-	dsb_coplthres->w ()->setDecimals (3);
-	dsb_coplthres->w ()->setValue (0.95f);
-	cb_condense->setChecked (true);
-	cb_subst->setChecked (true);
-	
-	QVBoxLayout* layout = new QVBoxLayout (&dlg);
-	layout->addWidget (cb_condense);
-	layout->addWidget (cb_subst);
-	layout->addWidget (cb_condlineCheck);
-	layout->addWidget (cb_colorize);
-	layout->addWidget (dsb_coplthres);
-	layout->addWidget (makeButtonBox (dlg));
-	
-	if (!dlg.exec ())
-		return;
-	
-	const bool condense = cb_condense->isChecked (),
-		subst = cb_subst->isChecked (),
-		condlineCheck = cb_condlineCheck->isChecked (),
-		colorize = cb_colorize->isChecked ();
-	const double coplthres = dsb_coplthres->w ()->value ();
-	
-	QTemporaryFile indat, outdat;
-	str inDATName, outDATName;
-	
-	// Make temp files for the input and output files
-	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
-		return;
-	
-	// Compose arguments
-	str argv = fmt ("%s %s %s %s -t %f %s %s",
-		(condense == false) ? "-q" : "",
-		(subst == false) ? "-r" : "",
-		(condlineCheck) ? "-a" : "",
-		(colorize) ? "-c" : "",
-		coplthres, inDATName.chars (), outDATName.chars ());
-	
-	writeSelection (inDATName);
-	runUtilityProcess (Rectifier, prog_rectifier, argv);
-	insertOutput (outDATName, true, {});
-}
-
-// =======================================================================================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =======================================================================================================================================
-// Intersector interface
-MAKE_ACTION (intersector, "Intersector", "intersector", "Perform clipping between two input groups.", KEY (F5)) {
-	setlocale (LC_ALL, "C");
-	
-	if (!checkProgPath (prog_intersector, Intersector))
-		return;
-	
-	QDialog dlg;
-	
-	LabeledWidget<QComboBox>* cmb_incol = new LabeledWidget<QComboBox> ("Input", new QComboBox),
-		*cmb_cutcol = new LabeledWidget<QComboBox> ("Cutter", new QComboBox);
-	QCheckBox* cb_colorize = new QCheckBox ("Colorize output"),
-		*cb_nocondense = new QCheckBox ("No condensing"),
-		*cb_repeatInverse = new QCheckBox ("Repeat inverse"),
-		*cb_edges = new QCheckBox ("Add edges");
-	LabeledWidget<QDoubleSpinBox>* dsb_prescale = new LabeledWidget<QDoubleSpinBox> ("Prescaling factor");
-	
-	cb_repeatInverse->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.");
-	cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection.");
-	
-	makeColorSelector (cmb_incol->w ());
-	makeColorSelector (cmb_cutcol->w ());
-	dsb_prescale->w ()->setMinimum (0.0f);
-	dsb_prescale->w ()->setMaximum (10000.0f);
-	dsb_prescale->w ()->setSingleStep (0.01f);
-	dsb_prescale->w ()->setValue (1.0f);
-	
-	QVBoxLayout* layout = new QVBoxLayout (&dlg);
-	layout->addWidget (cmb_incol);
-	layout->addWidget (cmb_cutcol);
-	
-	QHBoxLayout* cblayout = new QHBoxLayout;
-	cblayout->addWidget (cb_colorize);
-	cblayout->addWidget (cb_nocondense);
-	
-	QHBoxLayout* cb2layout = new QHBoxLayout;
-	cb2layout->addWidget (cb_repeatInverse);
-	cb2layout->addWidget (cb_edges);
-	
-	layout->addLayout (cblayout);
-	layout->addLayout (cb2layout);
-	layout->addWidget (dsb_prescale);
-	layout->addWidget (makeButtonBox (dlg));
-	
-exec:
-	if (!dlg.exec ())
-		return;
-	
-	const short inCol = cmb_incol->w ()->itemData (cmb_incol->w ()->currentIndex ()).toInt (),
-		cutCol =  cmb_cutcol->w ()->itemData (cmb_cutcol->w ()->currentIndex ()).toInt ();
-	const bool repeatInverse = cb_repeatInverse->isChecked ();
-	
-	if (inCol == cutCol) {
-		critical ("Cannot use the same color group for both input and cutter!");
-		goto exec;
-	}
-	
-	// Five temporary files!
-	// indat = input group file
-	// cutdat = cutter group file
-	// outdat = primary output
-	// outdat2 = inverse output
-	// edgesdat = edges output (isecalc)
-	QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat;
-	str inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
-	
-	if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) ||
-		!mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) ||
-		!mkTempFile (edgesdat, edgesDATName))
-	{
-		return;
-	}
-	
-	str parms = fmt ("%s %s -s %f",
-		(cb_colorize->isChecked ()) ? "-c" : "",
-		(cb_nocondense->isChecked ()) ? "-t" : "",
-		dsb_prescale->w ()->value ());
-	
-	str argv_normal = fmt ("%s %s %s %s", parms.chars (), inDATName.chars (), cutDATName.chars (), outDATName.chars ());
-	str argv_inverse = fmt ("%s %s %s %s", parms.chars (), cutDATName.chars (), inDATName.chars (), outDAT2Name.chars ());
-	
-	writeColorGroup (inCol, inDATName);
-	writeColorGroup (cutCol, cutDATName);
-	runUtilityProcess (Intersector, prog_intersector, argv_normal);
-	insertOutput (outDATName, false, {inCol});
-	
-	if (repeatInverse) {
-		runUtilityProcess (Intersector, prog_intersector, argv_inverse);
-		insertOutput (outDAT2Name, false, {cutCol});
-	}
-	
-	if (cb_edges->isChecked ()) {
-		runUtilityProcess (Isecalc, prog_isecalc, fmt ("%s %s %s", inDATName.chars (), cutDATName.chars (), edgesDATName.chars ()));
-		insertOutput (edgesDATName, false, {});
-	}
-}
\ No newline at end of file
--- a/extprogs.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef EXTPROGS_H
-#define EXTPROGS_H
-
-#include <qobject.h>
-
-enum extprog {
-	Isecalc,
-	Intersector,
-	Coverer,
-	Ytruder,
-	Rectifier,
-	DATHeader
-};
-
-#endif // EXTPROGS_H
\ No newline at end of file
--- a/file.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,757 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <vector>
-#include <stdio.h>
-#include <qmessagebox.h>
-#include <QDir>
-#include "common.h"
-#include "config.h"
-#include "file.h"
-#include "misc.h"
-#include "bbox.h"
-#include "gui.h"
-#include "history.h"
-#include "zz_ldrawPathDialog.h"
-
-cfg (str, io_ldpath, "");
-cfg (str, io_recentfiles, "");
-
-// =============================================================================
-namespace LDPaths {
-	static str pathError;
-	
-	struct {
-		str LDConfigPath;
-		str partsPath, primsPath;
-	} pathInfo;
-	
-	void initPaths () {
-		if (!tryConfigure (io_ldpath)) {
-			LDrawPathDialog dlg (false);
-			
-			if (!dlg.exec ())
-				exit (0);
-			
-			io_ldpath = dlg.path ();
-		}
-	}
-	
-	bool tryConfigure (str path) {
-		QDir dir;
-		
-		if (!dir.cd (path)) {
-			pathError = "Directory does not exist.";
-			return false;
-		}
-		
-		QStringList mustHave = {"LDConfig.ldr", "parts", "p"};
-		QStringList contents = dir.entryList (mustHave);
-		
-		if (contents.size () != mustHave.size ()) {
-			pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/.";
-			return false;
-		}
-		
-		pathInfo.partsPath = fmt ("%s" DIRSLASH "parts", path.chars ());
-		pathInfo.LDConfigPath = fmt ("%s" DIRSLASH "LDConfig.ldr", path.chars ());
-		pathInfo.primsPath = fmt ("%s" DIRSLASH "p", path.chars ());
-		
-		return true;
-	}
-	
-	// Accessors
-	str getError () { return pathError; }
-	str ldconfig () { return pathInfo.LDConfigPath; }
-	str prims () { return pathInfo.primsPath; }
-	str parts () { return pathInfo.partsPath; }
-}
-
-// =============================================================================
-OpenFile::OpenFile () {
-	m_implicit = true;
-	savePos = -1;
-}
-
-// =============================================================================
-OpenFile::~OpenFile () {
-	// Clear everything from the model
-	for (LDObject* obj : m_objs)
-		delete obj;
-	
-	// Clear the cache as well
-	for (LDObject* obj : m_objCache)
-		delete obj;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-OpenFile* findLoadedFile (str zName) {
-	for (OpenFile* file : g_loadedFiles)
-		if (file->m_filename == zName)
-			return file;
-	
-	return null;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-FILE* openLDrawFile (str path, bool bSubDirectories) {
-	str zTruePath = path;
-	
-#ifndef WIN32
-	zTruePath.replace ("\\", "/");
-#endif // WIN32
-	
-	FILE* fp = fopen (path.chars (), "r");
-	str zFilePath;
-	
-	if (fp != null)
-		return fp;
-	
-	if (~io_ldpath.value) {
-		// Try with just the LDraw path first
-		zFilePath = fmt ("%s" DIRSLASH "%s",
-			io_ldpath.value.chars(), zTruePath.chars());
-		logf ("Trying %s\n", zFilePath.chars());
-		
-		fp = fopen (zFilePath, "r");
-		if (fp != null)
-			return fp;
-		
-		if (bSubDirectories) {
-			char const* saSubdirectories[] = {
-				"parts",
-				"p",
-			};
-			
-			for (char const* sSubdir : saSubdirectories) {
-				zFilePath = fmt ("%s" DIRSLASH "%s" DIRSLASH "%s",
-					io_ldpath.value.chars(), sSubdir, zTruePath.chars());
-				printf ("try %s\n", zFilePath.chars());
-				
-				fp = fopen (zFilePath.chars (), "r");
-				
-				if (fp)
-					return fp;
-			}
-		}
-	}
-	
-	return null;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-std::vector<LDObject*> loadFileContents (FILE* fp, ulong* numWarnings) {
-	char line[1024];
-	vector<str> lines;
-	vector<LDObject*> objs;
-	ulong lnum = 0;
-	
-	if (numWarnings)
-		*numWarnings = 0;
-	
-	while (fgets (line, sizeof line, fp)) {
-		// Trim the trailing newline
-		str data = line;
-		while (data[~data - 1] == '\n' || data[~data - 1] == '\r')
-			data -= 1;
-		
-		LDObject* obj = parseLine (data);
-		assert (obj != null);
-		
-		// Check for parse errors and warn about tthem
-		if (obj->getType() == LDObject::Gibberish) {
-			logf (LOG_Warning, "Couldn't parse line #%lu: %s\n",
-				lnum, static_cast<LDGibberish*> (obj)->zReason.chars());
-			
-			logf (LOG_Warning, "- Line was: %s\n", data.chars());
-			
-			if (numWarnings)
-				(*numWarnings)++;
-		}
-		
-		objs.push_back (obj);
-		lnum++;
-	}
-	
-	return objs;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-OpenFile* openDATFile (str path, bool search) {
-	logf ("Opening %s...\n", path.chars());
-	
-	// Convert the file name to lowercase since some parts contain uppercase
-	// file names. I'll assume here that the library will always use lowercase
-	// file names for the actual parts..
-	FILE* fp;
-	if (search)
-		fp = openLDrawFile (-path, true);
-	else
-		fp = fopen (path, "r");
-	
-	if (!fp) {
-		logf (LOG_Error, "Couldn't open %s: %s\n", path.chars (), strerror (errno));
-		return null;
-	}
-	
-	OpenFile* load = new OpenFile;
-	load->m_filename = path;
-	ulong numWarnings;
-	std::vector<LDObject*> objs = loadFileContents (fp, &numWarnings);
-	
-	for (LDObject* obj : objs)
-		load->m_objs.push_back (obj);
-	
-	fclose (fp);
-	g_loadedFiles.push_back (load);
-	
-	logf ("File %s parsed successfully (%lu warning%s).\n",
-		path.chars(), numWarnings, PLURAL (numWarnings));
-	
-	return load;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-bool OpenFile::safeToClose () {
-	setlocale (LC_ALL, "C");
-	
-	// If we have unsaved changes, warn and give the option of saving.
-	if (!m_implicit && History::pos () != savePos) {
-		switch (QMessageBox::question (g_win, "Unsaved Changes",
-			fmt ("There are unsaved changes to %s. Should it be saved?", m_filename.chars ()),
-			(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel), QMessageBox::Cancel))
-		{
-		case QMessageBox::Yes:
-			if (!save ()) {
-				str errormsg = fmt ("Failed to save %s: %s\nDo you still want to close?",
-					m_filename.chars (), strerror (lastError));
-				
-				if (QMessageBox::critical (g_win, "Save Failure", errormsg,
-					(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::No)
-				{
-					return false;
-				}
-			}
-			
-			break;
-		
-		case QMessageBox::Cancel:
-			return false;
-		
-		default:
-			break;
-		}
-	}
-	
-	return true;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void closeAll () {
-	if (!g_loadedFiles.size())
-		return;
-	
-	// Remove all loaded files and the objects they contain
-	for (OpenFile* file : g_loadedFiles)
-		delete file;
-	
-	// Clear the array
-	g_loadedFiles.clear();
-	g_curfile = NULL;
-	
-	g_win->refresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void newFile () {
-	// Create a new anonymous file and set it to our current
-	closeAll ();
-	
-	OpenFile* f = new OpenFile;
-	f->m_filename = "";
-	g_loadedFiles.push_back (f);
-	g_curfile = f;
-	
-	g_BBox.calculate();
-	g_win->refresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void addRecentFile (str zPath) {
-	long lPos = io_recentfiles.value.first (zPath);
-	
-	// If this file already is in the list, pop it out.
-	if (lPos != -1) {
-		if (~io_recentfiles.value == ~zPath)
-			return; // only recent file - do nothing
-		
-		// Pop it out.
-		str zFront = io_recentfiles.value.substr (0, lPos);
-		str zBack = io_recentfiles.value.substr (lPos + ~zPath + 1, -1);
-		io_recentfiles.value = zFront + zBack;
-	}
-	
-	// If there's too many recent files, drop one out.
-	while (io_recentfiles.value.count ('@') > 3)
-		io_recentfiles.value = io_recentfiles.value.substr (io_recentfiles.value.first ("@") + 1, -1);
-	
-	// Add the file
-	if (~io_recentfiles.value > 0)
-		io_recentfiles.value += "@";
-	
-	io_recentfiles += zPath;
-	
-	config::save ();
-	g_win->updateRecentFilesMenu ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void openMainFile (str path) {
-	closeAll ();
-	
-	OpenFile* file = openDATFile (path, false);
-	
-	if (!file) {
-		// Tell the user loading failed.
-		setlocale (LC_ALL, "C");
-		critical (fmt ("Failed to open %s: %s", path.chars(), strerror (errno)));
-		return;
-	}
-	
-	file->m_implicit = false;
-	g_curfile = file;
-	
-	// Recalculate the bounding box
-	g_BBox.calculate();
-	
-	// Rebuild the object tree view now.
-	g_win->refresh ();
-	g_win->setTitle ();
-	g_win->R ()->resetAngles ();
-	
-	History::clear ();
-	
-	// Add it to the recent files list.
-	addRecentFile (path);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-bool OpenFile::save (str path) {
-	if (!~path)
-		path = m_filename;
-	
-	FILE* fp = fopen (path, "w");
-	
-	if (!fp) {
-		lastError = errno;
-		return false;
-	}
-	
-	// Write all entries now
-	for (LDObject* obj : m_objs) {
-		// LDraw requires lines to have DOS line endings
-		str zLine = fmt ("%s\r\n", obj->getContents ().chars ());
-		
-		fwrite (zLine.chars(), 1, ~zLine, fp);
-	}
-	
-	fclose (fp);
-	
-	// We have successfully saved, update the save position now.
-	savePos = History::pos ();
-	
-	g_win->setTitle ();
-	
-	return true;
-}
-
-#define CHECK_TOKEN_COUNT(N) \
-	if (tokens.size() != N) \
-		return new LDGibberish (zLine, "Bad amount of tokens");
-
-#define CHECK_TOKEN_NUMBERS(MIN,MAX) \
-	for (ushort i = MIN; i <= MAX; ++i) \
-		if (!isNumber (tokens[i])) \
-			return new LDGibberish (zLine, fmt ("Token #%u was `%s`, expected a number", \
-				(i + 1), tokens[i].chars()));
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-static vertex parseVertex (vector<str>& s, const ushort n) {
-	// Disable the locale while parsing the line or atof's behavior changes
-	// between locales (i.e. fails to read decimals properly). That is
-	// quite undesired...
-	setlocale (LC_NUMERIC, "C");
-	
-	vertex v;
-	for (const Axis ax : g_Axes)
-		v[ax] = atof (s[n + ax]);
-	
-	return v;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-LDObject* parseLine (str zLine) {
-	vector<str> tokens = zLine.split (" ", true);
-	
-	if (!tokens.size ()) {
-		// Line was empty, or only consisted of whitespace
-		return new LDEmpty;
-	}
-	
-	if (~tokens[0] != 1)
-		return new LDGibberish (zLine, "Illogical line code");
-	
-	const char c = tokens[0][0];
-	switch (c - '0') {
-	case 0:
-		{
-			// Comment
-			str comm;
-			for (uint i = 1; i < tokens.size(); ++i) {
-				comm += tokens[i];
-				
-				if (i != tokens.size() - 1)
-					comm += ' ';
-			}
-			
-			// Handle BFC statements
-			if (tokens.size() > 2 && tokens[1] == "BFC") {
-				for (short i = 0; i < LDBFC::NumStatements; ++i)
-					if (comm == fmt ("BFC %s", LDBFC::statements [i]))
-						return new LDBFC ((LDBFC::Type) i);
-				
-				// MLCAD is notorious for stuffing these statements in parts it
-				// creates. The above block only handles valid statements, so we
-				// need to handle MLCAD-style invertnext separately.
-				if (comm == "BFC CERTIFY INVERTNEXT")
-					return new LDBFC (LDBFC::InvertNext);
-			}
-			
-			if (tokens.size() > 2 && tokens[1] == "!LDFORGE") {
-				// Handle LDForge-specific types, they're embedded into comments
-				
-				if (tokens[2] == "VERTEX") {
-					// Vertex (0 !LDFORGE VERTEX)
-					CHECK_TOKEN_COUNT (7)
-					CHECK_TOKEN_NUMBERS (3, 6)
-					
-					LDVertex* obj = new LDVertex;
-					obj->dColor = atol (tokens[3]);
-					
-					for (const Axis ax : g_Axes)
-						obj->vPosition[ax] = atof (tokens[4 + ax]); // 4 - 6
-					
-					return obj;
-				}
-				
-				if (tokens[2] == "RADIAL") {
-					CHECK_TOKEN_COUNT (20)
-					CHECK_TOKEN_NUMBERS (4, 19)
-					
-					LDRadial::Type eType = LDRadial::NumTypes;
-					
-					for (int i = 0; i < LDRadial::NumTypes; ++i) {
-						if (str (LDRadial::radialTypeName ((LDRadial::Type) i)).toupper ().strip (' ') == tokens[3]) {
-							eType = (LDRadial::Type) i;
-							break;
-						}
-					}
-					
-					if (eType == LDRadial::NumTypes)
-						return new LDGibberish (zLine, fmt ("Unknown radial type %s", tokens[3].chars ()));
-					
-					LDRadial* obj = new LDRadial;
-					
-					obj->eRadialType = eType;			// 3
-					obj->dColor = atol (tokens[4]);		// 4
-					obj->dSegments = atol (tokens[5]);	// 5
-					obj->dDivisions = atol (tokens[6]);	// 6
-					obj->dRingNum = atol (tokens[7]);	// 7
-					
-					obj->vPosition = parseVertex (tokens, 8); // 8 - 10
-					
-					for (short i = 0; i < 9; ++i)
-						obj->mMatrix[i] = atof (tokens[i + 11]); // 11 - 19
-					
-					return obj;
-				}
-			}
-			
-			LDComment* obj = new LDComment;
-			obj->text = comm;
-			return obj;
-		}
-	
-	case 1:
-		{
-			// Subfile
-			CHECK_TOKEN_COUNT (15)
-			CHECK_TOKEN_NUMBERS (1, 13)
-			
-			// Try open the file
-			OpenFile* pFile = loadSubfile (tokens[14]);
-			
-			// If we cannot open the file, mark it an error
-			if (!pFile)
-				return new LDGibberish (zLine, "Could not open referred file");
-			
-			LDSubfile* obj = new LDSubfile;
-			obj->dColor = atol (tokens[1]);
-			obj->vPosition = parseVertex (tokens, 2); // 2 - 4
-			
-			for (short i = 0; i < 9; ++i)
-				obj->mMatrix[i] = atof (tokens[i + 5]); // 5 - 13
-			
-			obj->zFileName = tokens[14];
-			obj->pFile = pFile;
-			return obj;
-		}
-	
-	case 2:
-		{
-			CHECK_TOKEN_COUNT (8)
-			CHECK_TOKEN_NUMBERS (1, 7)
-			
-			// Line
-			LDLine* obj = new LDLine;
-			obj->dColor = atol (tokens[1]);
-			for (short i = 0; i < 2; ++i)
-				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 7
-			return obj;
-		}
-	
-	case 3:
-		{
-			CHECK_TOKEN_COUNT (11)
-			CHECK_TOKEN_NUMBERS (1, 10)
-			
-			// Triangle
-			LDTriangle* obj = new LDTriangle;
-			obj->dColor = atol (tokens[1]);
-			
-			for (short i = 0; i < 3; ++i)
-				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 10
-			
-			return obj;
-		}
-	
-	case 4:
-		{
-			CHECK_TOKEN_COUNT (14)
-			CHECK_TOKEN_NUMBERS (1, 13)
-			
-			// Quadrilateral
-			LDQuad* obj = new LDQuad;
-			obj->dColor = atol (tokens[1]);
-			
-			for (short i = 0; i < 4; ++i)
-				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 13
-			
-			return obj;
-		}
-	
-	case 5:
-		{
-			CHECK_TOKEN_COUNT (14)
-			CHECK_TOKEN_NUMBERS (1, 13)
-			
-			// Conditional line
-			LDCondLine* obj = new LDCondLine;
-			obj->dColor = atol (tokens[1]);
-			
-			for (short i = 0; i < 4; ++i)
-				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 13
-			
-			return obj;
-		}
-	
-	default: // Strange line we couldn't parse
-		return new LDGibberish (zLine, "Unknown line code number");
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-OpenFile* loadSubfile (str zFile) {
-	// Try open the file
-	OpenFile* pFile = findLoadedFile (zFile);
-	if (!pFile)
-		pFile = openDATFile (zFile, true);
-	
-	return pFile;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void reloadAllSubfiles () {
-	if (!g_curfile)
-		return;
-	
-	// First, close all but the current open file.
-	for (OpenFile* file : g_loadedFiles)
-		if (file != g_curfile)
-			delete file;
-	
-	g_loadedFiles.clear ();
-	g_loadedFiles.push_back (g_curfile);
-	
-	// Go through all objects in the current file and reload the subfiles
-	for (LDObject* obj : g_curfile->m_objs) {
-		if (obj->getType() == LDObject::Subfile) {
-			// Note: ref->pFile is invalid right now since all subfiles were closed.
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			OpenFile* pFile = loadSubfile (ref->zFileName);
-			
-			if (pFile)
-				ref->pFile = pFile;
-			else {
-				// Couldn't load the file, mark it an error
-				ref->replace (new LDGibberish (ref->getContents (), "Could not open referred file"));
-			}
-		}
-		
-		// Reparse gibberish files. It could be that they are invalid because
-		// the file could not be opened. Circumstances may be different now.
-		if (obj->getType() == LDObject::Gibberish)
-			obj->replace (parseLine (static_cast<LDGibberish*> (obj)->zContents));
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-ulong OpenFile::addObject (LDObject* obj) {
-	m_objs.push_back (obj);
-	
-	if (this == g_curfile)
-		g_BBox.calcObject (obj);
-	
-	return m_objs.size() - 1;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void OpenFile::insertObj (const ulong pos, LDObject* obj) {
-	m_objs.insert (m_objs.begin () + pos, obj);
-	
-	if (this == g_curfile)
-		g_BBox.calcObject (obj);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void OpenFile::forgetObject (LDObject* obj) {
-	// Find the index for the given object
-	ulong ulIndex;
-	for (ulIndex = 0; ulIndex < (ulong)m_objs.size(); ++ulIndex)
-		if (m_objs[ulIndex] == obj)
-			break; // found it
-	
-	if (ulIndex >= m_objs.size ())
-		return; // was not found
-	
-	// Erase it from memory
-	m_objs.erase (m_objs.begin() + ulIndex);
-	
-	// Update the bounding box
-	if (this == g_curfile)
-		g_BBox.calculate ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-std::vector<partListEntry> g_PartList;
-
-void initPartList () {
-	logf ("%s: initializing parts.lst\n", __func__);
-	
-	FILE* fp = openLDrawFile ("parts.lst", false);
-	
-	if (!fp)
-		return;
-	
-	char sLine[1024];
-	while (fgets (sLine, sizeof sLine, fp)) {
-		// Locate the first whitespace
-		char* cpWhite = strstr (sLine, " ");
-		
-		char sName[65];
-		size_t uLength = (cpWhite - sLine);
-		
-		if (uLength >= 64)
-			continue; // too long
-		
-		strncpy (sName, sLine, uLength);
-		sName[uLength] = '\0';
-		
-		// Surf through the whitespace sea!
-		while (*cpWhite == ' ')
-			cpWhite++;
-		
-		// Get the end point
-		char* cpEnd = strstr (sLine, "\r");
-		
-		if (cpEnd == null) {
-			// must not be DOS-formatted
-			cpEnd = strstr (sLine, "\n");
-		}
-		
-		assert (cpEnd != null);
-		
-		// Make the file title now
-		char sTitle[81];
-		uLength = (cpEnd - cpWhite);
-		strncpy (sTitle, cpWhite, uLength);
-		sTitle[uLength] = '\0';
-		
-		// Add it to the array.
-		partListEntry entry;
-		strcpy (entry.sName, sName);
-		strcpy (entry.sTitle, sTitle);
-		g_PartList.push_back (entry);
-	}
-}
\ No newline at end of file
--- a/file.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef FILE_H
-#define FILE_H
-
-#include "common.h"
-#include "ldtypes.h"
-#include "str.h"
-
-namespace LDPaths {
-	void initPaths ();
-	bool tryConfigure (str path);
-	
-	str ldconfig ();
-	str prims ();
-	str parts ();
-	str getError ();
-}
-
-// =============================================================================
-// OpenFile
-//
-// The OpenFile class stores a file opened in LDForge either as a editable file
-// for the user or for subfile caching. Its methods handle file input and output.
-// =============================================================================
-class OpenFile {
-public:
-	str m_filename, m_title;
-	vector<LDObject*> m_objs;
-	vector<LDObject*> m_objCache; // Cache of this file's contents, if desired
-	
-	int lastError;
-	
-	// Is this file implicit? Implicit files are opened automatically for
-	// caching purposes and are hidden from the user.
-	bool m_implicit;
-	
-	OpenFile ();
-	~OpenFile ();
-	
-	// Saves this file to disk.
-	bool save (str zPath = "");
-	
-	// Perform safety checks. Do this before closing any files!
-	bool safeToClose ();
-	
-	// Adds an object to this file at the end of the file.
-	ulong addObject (LDObject* obj);
-	
-	// Deletes the given object from the object chain.
-	void forgetObject (LDObject* obj);
-	
-	// At what point was this file last saved?
-	long savePos;
-	
-	LDObject* object (ulong pos) const {
-		return m_objs[pos];
-	}
-	
-	void insertObj (const ulong pos, LDObject* obj);
-};
-
-// Close all current loaded files and start off blank.
-void newFile ();
-
-// Opens the given file as the main file. Everything is closed first.
-void openMainFile (str zPath);
-
-// Finds an OpenFile by name or null if not open
-OpenFile* findLoadedFile (str name);
-
-// Opens the given file and parses the LDraw code within. Returns a pointer
-// to the opened file or null on error.
-OpenFile* openDATFile (str path, bool search);
-
-// Opens the given file and returns a pointer to it, potentially looking in /parts and /p
-FILE* openLDrawFile (str path, bool bSubDirectories);
-
-// Close all open files, whether user-opened or subfile caches.
-void closeAll ();
-
-// Parses a string line containing an LDraw object and returns the object parsed.
-LDObject* parseLine (str zLine);
-
-// Retrieves the pointer to - or loads - the given subfile.
-OpenFile* loadSubfile (str zFile);
-
-// Re-caches all subfiles.
-void reloadAllSubfiles ();
-
-typedef struct {
-	char sName[65], sTitle[81];
-} partListEntry;
-
-// Init and parse parts.lst
-void initPartList ();
-
-std::vector< LDObject* > loadFileContents (FILE* fp, ulong* numWarnings);
-
-extern vector<OpenFile*> g_loadedFiles;
-extern vector<partListEntry> g_PartList;
-
-#endif // FILE_H
\ No newline at end of file
--- a/gldraw.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1214 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QtGui>
-#include <QGLWidget>
-#include <GL/glu.h>
-#include "common.h"
-#include "config.h"
-#include "file.h"
-#include "gldraw.h"
-#include "bbox.h"
-#include "colors.h"
-#include "gui.h"
-#include "misc.h"
-#include "history.h"
-
-static double g_objOffset[3];
-
-static short g_pulseTick = 0;
-static const short g_numPulseTicks = 8;
-static const short g_pulseInterval = 65;
-
-static const struct staticCameraMeta {
-	const char glrotate[3];
-	const Axis axisX, axisY;
-	const bool negX, negY;
-} g_staticCameras[6] = {
-	{ { 1, 0, 0 }, X, Z, false, false },
-	{ { 0, 0, 0 }, X, Y, false, true },
-	{ { 0, 1, 0 }, Z, Y, true, true },
-	{ { -1, 0, 0 }, X, Z, false, true },
-	{ { 0, 0, 0 }, X, Y, true, true },
-	{ { 0, -1, 0 }, Z, Y, false, true },
-};
-
-cfg (str, gl_bgcolor, "#CCCCD9");
-cfg (str, gl_maincolor, "#707078");
-cfg (float, gl_maincolor_alpha, 1.0);
-cfg (int, gl_linethickness, 2);
-cfg (bool, gl_colorbfc, true);
-cfg (bool, gl_selflash, false);
-cfg (int, gl_camera, GLRenderer::Free);
-cfg (bool, gl_blackedges, true);
-cfg (bool, gl_axes, false);
-
-// CameraIcon::img is a heap-allocated QPixmap because otherwise it gets
-// initialized before program gets to main() and constructs a QApplication
-// and Qt doesn't like that.
-struct CameraIcon {
-	QPixmap* img;
-	QRect srcRect, destRect, selRect;
-	GLRenderer::Camera cam;
-} g_CameraIcons[7];
-
-const char* g_CameraNames[7] = { "Top", "Front", "Left", "Bottom", "Back", "Right", "Free" };
-
-const GLRenderer::Camera g_Cameras[7] = {
-	GLRenderer::Top,
-	GLRenderer::Front,
-	GLRenderer::Left,
-	GLRenderer::Bottom,
-	GLRenderer::Back,
-	GLRenderer::Right,
-	GLRenderer::Free
-};
-
-const struct GLAxis {
-	const QColor col;
-	const vertex vert;
-} g_GLAxes[3] = {
-	{ QColor (255, 0, 0), vertex (10000, 0, 0) },
-	{ QColor (128, 192, 0), vertex (0, 10000, 0) },
-	{ QColor (0, 160, 192), vertex (0, 0, 10000) },
-};
-	
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) {
-	resetAngles ();
-	m_picking = m_rangepick = false;
-	m_camera = (GLRenderer::Camera) gl_camera.value;
-	m_drawToolTip = false;
-	m_planeDraw = false;
-	
-	m_pulseTimer = new QTimer (this);
-	connect (m_pulseTimer, SIGNAL (timeout ()), this, SLOT (slot_timerUpdate ()));
-	
-	m_toolTipTimer = new QTimer (this);
-	m_toolTipTimer->setSingleShot (true);
-	connect (m_toolTipTimer, SIGNAL (timeout ()), this, SLOT (slot_toolTipTimer ()));
-	
-	m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
-	m_thinBorderPen = m_thickBorderPen;
-	m_thinBorderPen.setWidth (1);
-	
-	// Init camera icons
-	for (const GLRenderer::Camera cam : g_Cameras) {
-		str iconname;
-		iconname.format ("camera-%s", str (g_CameraNames[cam]).tolower ().chars ());
-		
-		CameraIcon* info = &g_CameraIcons[cam];
-		info->img = new QPixmap (getIcon (iconname));
-		info->cam = cam;
-	}
-	
-	calcCameraIconRects ();
-}
-
-// =============================================================================
-GLRenderer::~GLRenderer() {
-	for (CameraIcon& info : g_CameraIcons)
-		delete info.img;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::calcCameraIconRects () {
-	ushort i = 0;
-	
-	for (CameraIcon& info : g_CameraIcons) {
-		const long x1 = (m_width - (info.cam != Free ? 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::resetAngles () {
-	m_rotX = 30.0f;
-	m_rotY = 325.f;
-	m_panX = m_panY = m_rotZ = 0.0f;
-	
-	// Set the default zoom based on the bounding box
-	m_zoom = g_BBox.size () * 6;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::initializeGL () {
-	setBackground ();
-	
-	glEnable (GL_POLYGON_OFFSET_FILL);
-	glPolygonOffset (1.0f, 1.0f);
-	
-	glEnable (GL_DEPTH_TEST);
-	glShadeModel (GL_SMOOTH);
-	glEnable (GL_MULTISAMPLE);
-	
-	glEnable (GL_BLEND);
-	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-	
-	glEnable (GL_LINE_SMOOTH);
-	glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
-	
-	glLineWidth (gl_linethickness);
-	
-	setAutoFillBackground (false);
-	setMouseTracking (true);
-	setFocusPolicy (Qt::WheelFocus);
-	compileObjects ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-QColor GLRenderer::getMainColor () {
-	QColor col (gl_maincolor.value.chars());
-	
-	if (!col.isValid ())
-		return QColor (0, 0, 0);
-	
-	col.setAlpha (gl_maincolor_alpha * 255.f);
-	return col;
-}
-
-// -----------------------------------------------------------------------------
-void GLRenderer::setBackground () {
-	QColor col (gl_bgcolor.value.chars());
-	
-	if (!col.isValid ())
-		return;
-	
-	m_darkbg = luma (col) < 80;
-	
-	col.setAlpha (255);
-	qglClearColor (col);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-static vector<short> g_daWarnedColors;
-void GLRenderer::setObjectColor (LDObject* obj) {
-	QColor qcol;
-	
-	if (!obj->isColored())
-		return;
-	
-	if (m_picking) {
-		// Make the color by the object's index color if we're picking, so we can
-		// make the index from the color we get from the picking results.
-		long i = obj->getIndex (g_curfile);
-		
-		// If we couldn't find the index, this object must not be from this file,
-		// therefore it must be an object inlined from a subfile reference or
-		// decomposed from a radial. Find the top level parent object and use
-		// its index.
-		if (i == -1)
-			i = obj->topLevelParent ()->getIndex (g_curfile);
-		
-		// We should have the index now.
-		assert (i != -1);
-		
-		// Calculate a color based from this index. This method caters for
-		// 16777216 objects. I don't think that'll be exceeded anytime soon. :)
-		// ATM biggest is 53588.dat with 12600 lines.
-		double r = (i / (256 * 256)) % 256,
-			g = (i / 256) % 256,
-			b = i % 256;
-		
-		qglColor (QColor (r, g, b, 255));
-		return;
-	}
-	
-#if 0
-	if (gl_colorbfc &&
-		obj->getType () != LDObject::Line &&
-		obj->getType () != LDObject::CondLine)
-	{
-		if (bBackSide)
-			glColor4f (0.9f, 0.0f, 0.0f, 1.0f);
-		else
-			glColor4f (0.0f, 0.8f, 0.0f, 1.0f);
-		return;
-	}
-#endif
-	
-	if (obj->dColor == maincolor)
-		qcol = getMainColor ();
-	else {
-		color* col = getColor (obj->dColor);
-		qcol = col->qColor;
-	}
-	
-	if (obj->dColor == edgecolor) {
-		qcol = Qt::black;
-		color* col;
-		
-		if (!gl_blackedges && obj->parent != null && (col = getColor (obj->parent->dColor)) != null)
-			qcol = col->qEdge;
-	}
-	
-	if (qcol.isValid () == false) {
-		// The color was unknown. Use main color to make the object at least
-		// not appear pitch-black.
-		if (obj->dColor != edgecolor)
-			qcol = getMainColor ();
-		
-		// Warn about the unknown colors, but only once.
-		for (short i : g_daWarnedColors)
-			if (obj->dColor == i)
-				return;
-		
-		printf ("%s: Unknown color %d!\n", __func__, obj->dColor);
-		g_daWarnedColors.push_back (obj->dColor);
-		return;
-	}
-	
-	long r = qcol.red (),
-		g = qcol.green (),
-		b = qcol.blue (),
-		a = qcol.alpha ();
-	
-	// If it's selected, brighten it up, also pulse flash it if desired.
-	if (g_win->isSelected (obj)) {
-		short tick, numTicks;
-		
-		if (gl_selflash) {
-			tick = (g_pulseTick < (g_numPulseTicks / 2)) ? g_pulseTick : (g_numPulseTicks - g_pulseTick);
-			numTicks = g_numPulseTicks;
-		} else {
-			tick = 2;
-			numTicks = 5;
-		}
-		
-		const long add = ((tick * 128) / numTicks);
-		r = min (r + add, 255l);
-		g = min (g + add, 255l);
-		b = min (b + add, 255l);
-		
-		// a = 255;
-	}
-	
-	glColor4f (
-		((double) r) / 255.0f,
-		((double) g) / 255.0f,
-		((double) b) / 255.0f,
-		((double) a) / 255.0f);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::refresh () {
-	update ();
-	swapBuffers ();
-}
-
-// =============================================================================
-void GLRenderer::hardRefresh () {
-	compileObjects ();
-	refresh ();
-	
-	glLineWidth (gl_linethickness);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::resizeGL (int w, int h) {
-	m_width = w;
-	m_height = h;
-	
-	calcCameraIconRects ();
-	
-	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 () const {
-	if (g_curfile == null)
-		return;
-	
-	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-	glEnable (GL_DEPTH_TEST);
-	
-	if (m_camera != GLRenderer::Free) {
-		glMatrixMode (GL_PROJECTION);
-		glPushMatrix ();
-		
-		glLoadIdentity ();
-		glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f);
-		glTranslatef (m_panX, m_panY, 0.0f);
-		glRotatef (90.0f, g_staticCameras[m_camera].glrotate[0],
-			g_staticCameras[m_camera].glrotate[1],
-			g_staticCameras[m_camera].glrotate[2]);
-		
-		// Back camera needs to be handled differently
-		if (m_camera == GLRenderer::Back) {
-			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 (m_panX, m_panY, -m_zoom);
-		glRotatef (m_rotX, 1.0f, 0.0f, 0.0f);
-		glRotatef (m_rotY, 0.0f, 1.0f, 0.0f);
-		glRotatef (m_rotZ, 0.0f, 0.0f, 1.0f);
-	}
-	
-	for (LDObject* obj : g_curfile->m_objs) {
-		if (obj->hidden ())
-			continue; // Don't draw hidden objects
-		
-		glCallList (m_picking == false ? obj->uGLList : obj->uGLPickList);
-	}
-	
-	if (gl_axes && !m_picking)
-		glCallList (m_axeslist);
-	
-	glPopMatrix ();
-	glMatrixMode (GL_MODELVIEW);
-}
-
-// =============================================================================
-vertex GLRenderer::coord_2to3 (const QPoint& pos2d, const bool snap) const {
-	vertex pos3d;
-	const staticCameraMeta* cam = &g_staticCameras[m_camera];
-	const Axis axisX = cam->axisX;
-	const Axis axisY = cam->axisY;
-	const short 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) - m_panX) - (negXFac * g_objOffset[axisX]);
-	double cy = (m_virtHeight - ((2 * pos2d.y () * m_virtHeight) / m_height) - m_panY) - (negYFac * g_objOffset[axisY]);
-	
-	if (snap) {
-		cx = Grid::snap (cx, (Grid::Config) axisX);
-		cy = Grid::snap (cy, (Grid::Config) axisY);
-	}
-	
-	cx *= negXFac;
-	cy *= negYFac;
-	
-	pos3d = g_origin;
-	pos3d[axisX] = cx;
-	pos3d[axisY] = cy;
-	return pos3d;
-}
-
-// =============================================================================
-QPoint GLRenderer::coord_3to2 (const vertex& pos3d) const {
-	/*
-	cx = (-m_virtWidth + ((2 * pos2d.x () * m_virtWidth) / m_width) - m_panX) - (negXFac * g_objOffset[axisX]);
-	
-	                                                 cx = (-vw + ((2 * x * vw) / w) - panx) - (neg * ofs)
-	                                  cx + (neg * ofs) = (-vw + ((2 * x * vw) / w) - panx)
-	                                  cx + (neg * ofs) = ((2 * x * vw) / w) - vw - panx
-	                    (cx + (neg * ofs)) + vw + panx = (2 * x * vw) / w
-	              ((cx + (neg * ofs)) + vw + panx) * w = 2 * vw * x
-	
-	x = (((cx + (neg * ofs)) + vw + panx) * w) / (2 * vw)
-	*/
-	
-	QPoint pos2d;
-	const staticCameraMeta* cam = &g_staticCameras[m_camera];
-	const Axis axisX = cam->axisX;
-	const Axis axisY = cam->axisY;
-	const short negXFac = cam->negX ? -1 : 1,
-		negYFac = cam->negY ? -1 : 1;
-	
-	short x1 = (((pos3d[axisX] + (negXFac * g_objOffset[axisX])) +
-		m_virtWidth + m_panX) * m_width) / (2 * m_virtWidth);
-	short y1 = -(((pos3d[axisY] + (negYFac * g_objOffset[axisY])) -
-		m_virtHeight + m_panY) * m_height) / (2 * m_virtHeight);
-	
-	x1 *= negXFac;
-	y1 *= negYFac;
-	
-	pos2d = QPoint (x1, y1);
-	return pos2d;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::paintEvent (QPaintEvent* ev) {
-	Q_UNUSED (ev)
-	m_virtWidth = m_zoom;
-	m_virtHeight = (m_height * m_virtWidth) / m_width;
-	drawGLScene ();
-	
-	QPainter paint (this);
-	QFontMetrics metrics = QFontMetrics (QFont ());
-	paint.setRenderHint (QPainter::Antialiasing);
-	
-	m_hoverpos = g_origin;
-	
-	if (m_camera != Free) {
-		// Calculate 3d position of the cursor
-		m_hoverpos = coord_2to3 (m_pos, true);
-		
-		// Paint the coordinates onto the screen.
-		str text;
-		text.format ("X: %s, Y: %s, Z: %s", ftoa (m_hoverpos[X]).chars (),
-			ftoa (m_hoverpos[Y]).chars (), ftoa (m_hoverpos[Z]).chars ());
-		
-		QFontMetrics metrics = QFontMetrics (font ());
-		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
-		
-		paint.drawText (m_width - textSize.width (), m_height - 16, textSize.width (),
-			textSize.height (), Qt::AlignCenter, text);
-		
-		// If we're plane drawing, draw the vertices onto the screen.
-		if (m_planeDraw) {
-			ushort numverts = m_planeDrawVerts.size () + 1;
-			const short blipsize = 8;
-			
-			if (numverts > 0) {
-				QPoint* poly = new QPoint[numverts];
-				
-				uchar i = 0;
-				for (vertex& vert : m_planeDrawVerts) {
-					poly[i] = coord_3to2 (vert);
-					++i;
-				}
-				
-				// Draw the cursor vertex as the last one in the list.
-				if (numverts < 5)
-					poly[i] = coord_3to2 (m_hoverpos);
-				else
-					numverts = 4;
-				
-				paint.setPen (m_thinBorderPen);
-				paint.setBrush (QColor (128, 192, 0));
-				
-				// Draw vertex blips
-				for (ushort i = 0; i < numverts; ++i) {
-					QPoint& blip = poly[i];
-					paint.drawEllipse (blip.x () - blipsize / 2, blip.y () - blipsize / 2,
-						blipsize, blipsize);
-				}
-				
-				paint.setPen (m_thickBorderPen);
-				paint.setBrush (QColor (128, 192, 0, 128));
-				paint.drawPolygon (poly, numverts);
-				
-				delete[] poly;
-			}
-		}
-	}
-	
-	// Camera icons
-	if (!m_picking && m_planeDraw == false) {
-		// Draw a background for the selected camera
-		paint.setPen (m_thinBorderPen);
-		paint.setBrush (QBrush (QColor (0, 128, 160, 128)));
-		paint.drawRect (g_CameraIcons[camera ()].selRect);
-		
-		// Draw the actual icons
-		for (CameraIcon& info : g_CameraIcons)
-			paint.drawPixmap (info.destRect, *info.img, info.srcRect);
-		
-		// Draw a label for the current camera in the top left corner
-		{
-			const ushort margin = 4;
-			
-			str label;
-			label.format ("%s Camera", g_CameraNames[camera ()]);
-			paint.setBrush (Qt::black);
-			paint.drawText (QPoint (margin, margin + metrics.ascent ()), label);
-		}
-		
-		// Tool tips
-		if (m_drawToolTip) {
-			if (g_CameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false)
-				m_drawToolTip = false;
-			else {
-				QPen bord = m_thinBorderPen;
-				bord.setBrush (Qt::black);
-				
-				const ushort margin = 2;
-				ushort x0 = m_pos.x (),
-					y0 = m_pos.y ();
-				
-				str label;
-				label.format ("%s Camera", g_CameraNames[m_toolTipCamera]);
-				
-				const ushort textWidth = metrics.width (label),
-					textHeight = metrics.height (),
-					fullWidth = textWidth + (2 * margin),
-					fullHeight = textHeight + (2 * margin);
-				
-				QRect area (m_pos.x (), m_pos.y (), fullWidth, fullHeight);
-				
-				if (x0 + fullWidth > m_width)
-					x0 -= fullWidth;
-				
-				if (y0 + fullHeight > m_height)
-					y0 -= fullHeight;
-				
-				paint.setBrush (QColor (0, 128, 255, 208));
-				paint.setPen (bord);
-				paint.drawRect (x0, y0, fullWidth, fullHeight);
-				
-				paint.setBrush (Qt::black);
-				paint.drawText (QPoint (x0 + margin, y0 + margin + metrics.ascent ()), label);
-			}
-		}
-	}
-	
-	// If we're range-picking, draw a rectangle encompassing the selection area.
-	if (m_rangepick && !m_picking) {
-		const short x0 = m_rangeStart.x (),
-			y0 = m_rangeStart.y (),
-			x1 = m_pos.x (),
-			y1 = m_pos.y ();
-		
-		QRect rect (x0, y0, x1 - x0, y1 - y0);
-		QColor fillColor = (m_addpick ? "#80FF00" : "#00CCFF");
-		fillColor.setAlphaF (0.2f);
-		
-		paint.setPen (m_thickBorderPen);
-		paint.setBrush (QBrush (fillColor));
-		paint.drawRect (rect);
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::compileObjects () {
-	if (g_BBox.empty () == false) {
-		g_objOffset[X] = -(g_BBox.v0 ()[X] + g_BBox.v1 ()[X]) / 2;
-		g_objOffset[Y] = -(g_BBox.v0 ()[Y] + g_BBox.v1 ()[Y]) / 2;
-		g_objOffset[Z] = -(g_BBox.v0 ()[Z] + g_BBox.v1 ()[Z]) / 2;
-	} else {
-		// use a default bbox if we need 
-		g_objOffset[X] = g_objOffset[Y] = g_objOffset[Z] = 0;
-	}
-	
-	if (!g_curfile) {
-		printf ("renderer: no files loaded, cannot compile anything\n");
-		return;
-	}
-	
-	for (LDObject* obj : g_curfile->m_objs) {
-		GLuint* upaLists[2] = {
-			&obj->uGLList,
-			&obj->uGLPickList
-		};
-		
-		for (GLuint* upMemberList : upaLists) {
-			GLuint uList = glGenLists (1);
-			glNewList (uList, GL_COMPILE);
-			
-			m_picking = (upMemberList == &obj->uGLPickList);
-			compileOneObject (obj);
-			m_picking = false;
-			
-			glEndList ();
-			*upMemberList = uList;
-		}
-	}
-	
-	// Compile axes
-	m_axeslist = glGenLists (1);
-	glNewList (m_axeslist, GL_COMPILE);
-	glBegin (GL_LINES);
-	for (const GLAxis& ax : g_GLAxes) {
-		qglColor (ax.col);
-		compileVertex (ax.vert);
-		compileVertex (-ax.vert);
-	}
-	glEnd ();
-	glEndList ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype) {
-	glBegin (gltype);
-	
-	const short numverts = (obj->getType () != LDObject::CondLine) ? obj->vertices () : 2;
-	
-	for (short i = 0; i < numverts; ++i)
-		compileVertex (obj->vaCoords[i]);
-	
-	glEnd ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::compileOneObject (LDObject* obj) {
-	setObjectColor (obj);
-	
-	switch (obj->getType ()) {
-	case LDObject::Line:
-		compileSubObject (obj, GL_LINES);
-		break;
-	
-	case LDObject::CondLine:
-		glLineStipple (1, 0x6666);
-		glEnable (GL_LINE_STIPPLE);
-		
-		compileSubObject (obj, GL_LINES);
-		
-		glDisable (GL_LINE_STIPPLE);
-		break;
-	
-	case LDObject::Triangle:
-		compileSubObject (obj, GL_TRIANGLES);
-		break;
-	
-	case LDObject::Quad:
-		compileSubObject (obj, GL_QUADS);
-		break;
-	
-	case LDObject::Subfile:
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			vector<LDObject*> objs = ref->inlineContents (true, true);
-			
-			for (LDObject* obj : objs) {
-				compileOneObject (obj);
-				delete obj;
-			}
-		}
-		break;
-	
-	case LDObject::Radial:
-		{
-			LDRadial* pRadial = static_cast<LDRadial*> (obj);
-			std::vector<LDObject*> objs = pRadial->decompose (true);
-			
-			for (LDObject* obj : objs) {
-				compileOneObject (obj);
-				delete obj;
-			}
-		}
-		break;
-	
-#if 0
-	TODO: find a proper way to draw vertices without having them be affected by zoom.
-	case LDObject::Vertex:
-		{
-			LDVertex* pVert = static_cast<LDVertex*> (obj);
-			LDTriangle* pPoly;
-			vertex* vPos = &(pVert->vPosition);
-			const double fPolyScale = max (fZoom, 1.0);
-			
-#define BIPYRAMID_COORD(N) ((((i + N) % 4) >= 2 ? 1 : -1) * 0.3f * fPolyScale)
-			
-			for (int i = 0; i < 8; ++i) {
-				pPoly = new LDTriangle;
-				pPoly->vaCoords[0] = {vPos->x, vPos->y + ((i >= 4 ? 1 : -1) * 0.4f * fPolyScale), vPos->z};
-				pPoly->vaCoords[1] = {
-					vPos->x + BIPYRAMID_COORD (0),
-					vPos->y,
-					vPos->z + BIPYRAMID_COORD (1)
-				};
-				
-				pPoly->vaCoords[2] = {
-					vPos->x + BIPYRAMID_COORD (1),
-					vPos->y,
-					vPos->z + BIPYRAMID_COORD (2)
-				};
-				
-				pPoly->dColor = pVert->dColor;
-				compileOneObject (pPoly);
-				delete pPoly;
-			}
-		}
-		break;
-#endif // 0
-	
-	default:
-		break;
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::compileVertex (const vertex& vrt) {
-	glVertex3d (
-		(vrt[X] + g_objOffset[0]),
-		-(vrt[Y] + g_objOffset[1]),
-		-(vrt[Z] + g_objOffset[2]));
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::clampAngle (double& angle) {
-	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) && !(ev->buttons() & Qt::LeftButton);
-	const bool wasRight = (m_lastButtons & Qt::RightButton) && !(ev->buttons() & Qt::RightButton);
-	
-	if (wasLeft) {
-		if (m_planeDraw) {
-			// If we picked an already-existing vertex, stop drawing
-			for (vertex& vert : m_planeDrawVerts) {
-				if (vert == m_hoverpos) {
-					endPlaneDraw (true);
-					return;
-				}
-			}
-			
-			// Also, if have 4 verts, also stop drawing.
-			if (m_planeDrawVerts.size () >= 4)
-				endPlaneDraw (true);
-			
-			m_planeDrawVerts.push_back (m_hoverpos);
-			
-			update ();
-			return;
-		} else {
-			if (!m_rangepick)
-				m_addpick = (m_keymods & Qt::ControlModifier);
-			
-			if (m_totalmove < 10 || m_rangepick)
-				pick (ev->x (), ev->y ());
-		}
-		
-		m_rangepick = false;
-		m_totalmove = 0;
-		return;
-	}
-	
-	if (wasRight && m_planeDraw) {
-		if (m_planeDrawVerts.size () > 0) {
-			// Remove the last vertex
-			m_planeDrawVerts.erase (m_planeDrawVerts.end () - 1);
-		} else {
-			endPlaneDraw (false);
-			return;
-		}
-		
-		update ();
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::mousePressEvent (QMouseEvent* ev) {
-	if (ev->buttons () & Qt::LeftButton)
-		m_totalmove = 0;
-	
-	if (ev->modifiers () & Qt::ShiftModifier) {
-		m_rangepick = true;
-		m_rangeStart.setX (ev->x ());
-		m_rangeStart.setY (ev->y ());
-		
-		m_addpick = (m_keymods & Qt::ControlModifier);
-	}
-	
-	m_lastButtons = ev->buttons ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::mouseMoveEvent (QMouseEvent* ev) {
-	int dx = ev->x () - m_pos.x ();
-	int dy = ev->y () - m_pos.y ();
-	m_totalmove += abs (dx) + abs (dy);
-	
-	if (ev->buttons () & Qt::LeftButton && !m_rangepick) {
-		m_rotX = m_rotX + (dy);
-		m_rotY = m_rotY + (dx);
-		
-		clampAngle (m_rotX);
-		clampAngle (m_rotY);
-	}
-	
-	if (ev->buttons () & Qt::MidButton) {
-		m_panX += 0.03f * dx * (m_zoom / 7.5f);
-		m_panY -= 0.03f * dy * (m_zoom / 7.5f);
-	}
-	
-	// Start the tool tip timer
-	if (!m_drawToolTip)
-		m_toolTipTimer->start (1000);
-	
-	m_pos = ev->pos ();
-	update ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::keyPressEvent (QKeyEvent* ev) {
-	m_keymods = ev->modifiers ();
-}
-
-void GLRenderer::keyReleaseEvent (QKeyEvent* ev) {
-	m_keymods = ev->modifiers ();
-}
-
-// =============================================================================
-void GLRenderer::wheelEvent (QWheelEvent* ev) {
-	m_zoom *= (ev->delta () < 0) ? 1.2f : (1.0f / 1.2f);
-	m_zoom = clamp (m_zoom, 0.01, 10000.0);
-	
-	update ();
-	ev->accept ();
-}
-
-// =============================================================================
-void GLRenderer::leaveEvent (QEvent* ev) {
-	Q_UNUSED (ev);
-	m_drawToolTip = false;
-	m_toolTipTimer->stop ();
-	update ();
-}
-
-// =============================================================================
-void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) {
-	g_win->spawnContextMenu (ev->globalPos ());
-}
-
-// =============================================================================
-void GLRenderer::setCamera (const GLRenderer::Camera cam) {
-	m_camera = cam;
-	gl_camera = (int) cam;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::updateSelFlash () {
-	if (gl_selflash && g_win->sel ().size() > 0) {
-		m_pulseTimer->start (g_pulseInterval);
-		g_pulseTick = 0;
-	} else
-		m_pulseTimer->stop ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::pick (uint mouseX, uint mouseY) {
-	// Check if we selected a camera icon
-	if (!m_rangepick) {
-		QPoint pos (mouseX, mouseY);
-		
-		for (CameraIcon& info : g_CameraIcons) {
-			if (info.destRect.contains (pos)) {
-				setCamera (info.cam);
-				update ();
-				return;
-			}
-		}
-	}
-	
-	GLint viewport[4];
-	LDObject* removedObject = null;
-	
-	// Clear the selection if we do not wish to add to it.
-	if (!m_addpick) {
-		std::vector<LDObject*> oldsel = g_win->sel ();
-		g_win->sel ().clear ();
-		
-		// Recompile the prior selection to remove the highlight color
-		for (LDObject* obj : oldsel)
-			recompileObject (obj);
-	}
-	
-	m_picking = true;
-	
-	// Paint the picking scene
-	glDisable (GL_DITHER);
-	glClearColor (1.0f, 1.0f, 1.0f, 1.0f);
-	drawGLScene ();
-	
-	glGetIntegerv (GL_VIEWPORT, viewport);
-	
-	short x0 = mouseX,
-		y0 = mouseY;
-	short x1, y1;
-	
-	// Determine how big an area to read - with range picking, we pick by
-	// the area given, with single pixel picking, we use an 1 x 1 area.
-	if (m_rangepick) {
-		x1 = m_rangeStart.x ();
-		y1 = m_rangeStart.y ();
-	} else {
-		x1 = x0 + 1;
-		y1 = y0 + 1;
-	}
-	
-	// x0 and y0 must be less than x1 and y1, respectively.
-	if (x0 > x1)
-		dataswap (x0, x1);
-	
-	if (y0 > y1)
-		dataswap (y0, y1);
-	
-	// Clamp the values to ensure they're within bounds
-	x0 = max<short> (0, x0);
-	y0 = max<short> (0, y0);
-	x1 = min<short> (x1, m_width);
-	y1 = min<short> (y1, m_height);
-	
-	const short areawidth = (x1 - x0);
-	const short areaheight = (y1 - y0);
-	const long numpixels = areawidth * areaheight;
-	
-	// Allocate space for the pixel data.
-	uchar* const pixeldata = new uchar[4 * numpixels];
-	uchar* pixelptr = &pixeldata[0];
-	
-	assert (viewport[3] == m_height);
-	
-	// Read pixels from the color buffer.
-	glReadPixels (x0, viewport[3] - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata);
-	
-	// Go through each pixel read and add them to the selection.
-	for (long i = 0; i < numpixels; ++i) {
-		uint32 idx =
-			(*(pixelptr) * 0x10000) +
-			(*(pixelptr + 1) * 0x00100) +
-			(*(pixelptr + 2) * 0x00001);
-		pixelptr += 4;
-		
-		if (idx == 0xFFFFFF)
-			continue; // White is background; skip
-		
-		LDObject* obj = g_curfile->object (idx);
-		
-		// If this is an additive single pick and the object is currently selected,
-		// we remove it from selection instead.
-		if (!m_rangepick && m_addpick) {
-			bool removed = false;
-			
-			for (ulong i = 0; i < g_win->sel ().size(); ++i) {
-				if (g_win->sel ()[i] == obj) {
-					g_win->sel ().erase (g_win->sel ().begin () + i);
-					removedObject = obj;
-					removed = true;
-				}
-			}
-			
-			if (removed)
-				break;
-		}
-		
-		g_win->sel ().push_back (obj);
-	}
-	
-	delete[] pixeldata;
-	
-	// Remove duplicate entries. For this to be effective, the vector must be
-	// sorted first.
-	std::vector<LDObject*>& sel = g_win->sel ();
-	std::sort (sel.begin(), sel.end ());
-	std::vector<LDObject*>::iterator pos = std::unique (sel.begin (), sel.end ());
-	sel.resize (std::distance (sel.begin (), pos));
-	
-	// Update everything now.
-	g_win->buildObjList ();
-	
-	m_picking = false;
-	m_rangepick = false;
-	glEnable (GL_DITHER);
-	
-	setBackground ();
-	updateSelFlash ();
-	
-	for (LDObject* obj : g_win->sel ())
-		recompileObject (obj);
-	
-	if (removedObject != null)
-		recompileObject (removedObject);
-	
-	drawGLScene ();
-	swapBuffers ();
-	update ();
-}
-
-// =============================================================================
-void GLRenderer::beginPlaneDraw () {
-	if (m_camera == Free)
-		return; // Cannot draw with the free camera
-	
-	m_planeDraw = true;
-	
-	// Disable the context menu - we need the right mouse button
-	// for removing vertices.
-	setContextMenuPolicy (Qt::NoContextMenu);
-	
-	// Use the crosshair cursor when plane drawing.
-	setCursor (Qt::CrossCursor);
-	
-	// Clear the selection when beginning to draw onto a plane.
-	// FIXME: make the selection clearing stuff in ::pick a method and use it
-	// here! This code doesn't update the GL lists.
-	g_win->sel ().clear ();
-	g_win->updateSelection ();
-	update ();
-}
-
-// =============================================================================
-void GLRenderer::endPlaneDraw (bool accept) {
-	// If we accepted, clean the selection and create the object
-	if (accept) {
-		vector<vertex>& verts = m_planeDrawVerts;
-		LDObject* obj = null;
-		
-		switch (verts.size ()) {
-		case 1:
-			{
-				// 1 vertex - add a vertex object
-				obj = new LDVertex;
-				static_cast<LDVertex*> (obj)->vPosition = verts[0];
-				obj->dColor = maincolor;
-			}
-			break;
-		
-		case 2:
-			{
-				// 2 verts - make a line
-				obj = new LDLine;
-				obj->dColor = edgecolor;
-				for (ushort i = 0; i < 2; ++i)
-					obj->vaCoords[i] = verts[i];
-			}
-			break;
-			
-		case 3:
-		case 4:
-			{
-				obj = (verts.size () == 3) ?
-					static_cast<LDObject*> (new LDTriangle) :
-					static_cast<LDObject*> (new LDQuad);
-				
-				obj->dColor = maincolor;
-				for (ushort i = 0; i < obj->vertices (); ++i)
-					obj->vaCoords[i] = verts[i];
-			}
-			break;
-		}
-		
-		if (obj) {
-			g_curfile->addObject (obj);
-			recompileObject (obj);
-			g_win->refresh ();
-			
-			History::addEntry (new AddHistory ({(ulong) obj->getIndex (g_curfile)}, {obj->clone ()}));
-		}
-	}
-	
-	m_planeDraw = false;
-	m_planeDrawVerts.clear ();
-	
-	unsetCursor ();
-	
-	// Restore the context menu
-	setContextMenuPolicy (Qt::DefaultContextMenu);
-	
-	update ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void GLRenderer::recompileObject (LDObject* obj) {
-	// Replace the old list with the new one.
-	GLuint uList = glGenLists (1);
-	glNewList (uList, GL_COMPILE);
-	
-	compileOneObject (obj);
-	
-	glEndList ();
-	obj->uGLList = uList;
-}
-
-// =============================================================================
-uchar* GLRenderer::screencap (ushort& w, ushort& 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);
-	
-	// Restore the background
-	setBackground ();
-	
-	return cap;
-}
-
-// =============================================================================
-void GLRenderer::slot_timerUpdate () {
-	++g_pulseTick %= g_numPulseTicks;
-	
-	for (LDObject* obj : g_win->sel ())
-		recompileObject (obj);
-	
-	update ();
-}
-
-// =============================================================================
-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 : g_CameraIcons) {
-		if (icon.destRect.contains (m_pos)) {
-			m_toolTipCamera = icon.cam;
-			m_drawToolTip = true;
-			update ();
-			break;
-		}
-	}
-}
\ No newline at end of file
--- a/gldraw.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef GLDRAW_H
-#define GLDRAW_H
-
-#include <QGLWidget>
-#include <qtimer.h>
-#include "common.h"
-#include "ldtypes.h"
-
-// =============================================================================
-// GLRenderer
-// 
-// The main renderer object, draws the brick on the screen, manages the camera
-// and selection picking. The instance of GLRenderer is accessible as
-// g_win->R ()
-// =============================================================================
-class GLRenderer : public QGLWidget {
-	Q_OBJECT
-	
-public:
-	enum Camera {
-		Top,
-		Front,
-		Left,
-		Bottom,
-		Back,
-		Right,
-		Free
-	};
-	
-	GLRenderer (QWidget* parent = null);
-	~GLRenderer ();
-	
-	void hardRefresh ();
-	void compileObjects ();
-	void setBackground ();
-	void pick (uint mouseX, uint mouseY);
-	QColor getMainColor ();
-	void recompileObject (LDObject* obj);
-	void refresh ();
-	void updateSelFlash ();
-	void resetAngles ();
-	uchar* screencap (ushort& w, ushort& h);
-	void setCamera (const GLRenderer::Camera cam);
-	void beginPlaneDraw ();
-	void endPlaneDraw (bool accept);
-	
-	GLRenderer::Camera camera () { return m_camera; }
-	bool picking () { return m_picking; }
-	void setZoom (const double zoom) { m_zoom = zoom; }
-	double zoom () const { return m_zoom; }
-
-protected:
-	void initializeGL ();
-	void resizeGL (int w, int h);
-	
-	void mousePressEvent (QMouseEvent* ev);
-	void mouseMoveEvent (QMouseEvent* ev);
-	void mouseReleaseEvent (QMouseEvent* ev);
-	void keyPressEvent (QKeyEvent* ev);
-	void keyReleaseEvent (QKeyEvent* ev);
-	void wheelEvent (QWheelEvent* ev);
-	void paintEvent (QPaintEvent* ev);
-	void leaveEvent (QEvent* ev);
-	void contextMenuEvent (QContextMenuEvent* ev);
-
-private:
-	QTimer* m_pulseTimer, *m_toolTipTimer;
-	Qt::MouseButtons m_lastButtons;
-	Qt::KeyboardModifiers m_keymods;
-	ulong m_totalmove;
-	vertex m_hoverpos;
-	double m_virtWidth, m_virtHeight, m_rotX, m_rotY, m_rotZ, m_panX, m_panY, m_zoom;
-	bool m_darkbg, m_picking, m_rangepick, m_addpick, m_drawToolTip, m_screencap, m_planeDraw;
-	QPoint m_pos, m_rangeStart;
-	QPen m_thinBorderPen, m_thickBorderPen;
-	Camera m_camera, m_toolTipCamera;
-	uint m_axeslist;
-	ushort m_width, m_height;
-	std::vector<vertex> m_planeDrawVerts;
-	
-	void compileOneObject (LDObject* obj);
-	void compileSubObject (LDObject* obj, const GLenum gltype);
-	void compileVertex (const vertex& vrt);
-	void clampAngle (double& angle);
-	void setObjectColor (LDObject* obj);
-	void drawGLScene () const;
-	void calcCameraIconRects ();
-	
-	vertex coord_2to3 (const QPoint& pos2d, const bool snap) const;
-	QPoint coord_3to2 (const vertex& pos3d) const;
-	
-private slots:
-	void slot_timerUpdate ();
-	void slot_toolTipTimer ();
-};
-
-#endif // GLDRAW_H
\ No newline at end of file
--- a/gui.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1025 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <qgridlayout.h>
-#include <qmessagebox.h>
-#include <qevent.h>
-#include <qmenubar.h>
-#include <qstatusbar.h>
-#include <qsplitter.h>
-#include <qlistwidget.h>
-#include <qtoolbutton.h>
-#include <qcombobox.h>
-#include <qcoreapplication.h>
-#include "common.h"
-#include "gldraw.h"
-#include "gui.h"
-#include "file.h"
-#include "config.h"
-#include "misc.h"
-#include "colors.h"
-#include "history.h"
-#include "config.h"
-
-vector<actionmeta> g_ActionMeta;
-
-static bool g_bSelectionLocked = false;
-
-cfg (bool, lv_colorize, true);
-cfg (int, gui_toolbar_iconsize, 24);
-cfg (str, gui_colortoolbar, "16:24:|:0:1:2:3:4:5:6:7");
-extern_cfg (str, io_recentfiles);
-extern_cfg (bool, gl_axes);
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-ForgeWindow::ForgeWindow () {
-	g_win = this;
-	m_renderer = new GLRenderer;
-	
-	m_objList = new ObjectList;
-	m_objList->setSelectionMode (QListWidget::ExtendedSelection);
-	m_objList->setAlternatingRowColors (true);
-	connect (m_objList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_selectionChanged ()));
-	
-	m_splitter = new QSplitter;
-	m_splitter->addWidget (m_renderer);
-	m_splitter->addWidget (m_objList);
-	setCentralWidget (m_splitter);
-	
-	m_colorMeta = parseQuickColorMeta ();
-	
-	createMenuActions ();
-	createMenus ();
-	createToolbars ();
-	
-	slot_selectionChanged ();
-	
-	setStatusBar (new QStatusBar);
-	setWindowIcon (getIcon ("ldforge"));
-	setTitle ();
-	setMinimumSize (320, 200);
-	resize (800, 600);
-	
-	connect (QCoreApplication::instance (), SIGNAL (aboutToQuit ()), this, SLOT (slot_lastSecondCleanup ()));
-}
-
-// =============================================================================
-void ForgeWindow::slot_lastSecondCleanup () {
-	m_renderer->setParent (null);
-	delete m_renderer;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::createMenuActions () {
-	// Create the actions based on stored meta.
-	for (actionmeta meta : g_ActionMeta) {
-		QAction*& qAct = *meta.qAct;
-		qAct = new QAction (getIcon (meta.sIconName), meta.sDisplayName, this);
-		qAct->setStatusTip (meta.sDescription);
-		qAct->setShortcut (*meta.conf);
-		
-		connect (qAct, SIGNAL (triggered ()), this, SLOT (slot_action ()));
-	}
-	
-	// Grid actions and axes are checkable
-	findAction ("gridCoarse")->setCheckable (true);
-	findAction ("gridMedium")->setCheckable (true);
-	findAction ("gridFine")->setCheckable (true);
-	
-	findAction ("axes")->setCheckable (true);
-	findAction ("axes")->setChecked (gl_axes);
-	
-	// things not implemented yet
-	findAction ("help")->setEnabled (false);
-	
-	History::updateActions ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-QMenu* g_CurrentMenu;
-
-QMenu* ForgeWindow::initMenu (const char* name) {
-	return g_CurrentMenu = menuBar ()->addMenu (tr (name));
-}
-
-void ForgeWindow::addMenuAction (const char* name) {
-	g_CurrentMenu->addAction (findAction (name));
-}
-
-
-// =============================================================================
-void ForgeWindow::createMenus () {
-	m_recentFilesMenu = new QMenu (tr ("Open &Recent"));
-	m_recentFilesMenu->setIcon (getIcon ("open-recent"));
-	updateRecentFilesMenu ();
-	
-	QMenu*& menu = g_CurrentMenu;
-	
-	// File menu
-	initMenu ("&File");
-	addMenuAction ("newFile");				// New
-	addMenuAction ("open");				// Open
-	menu->addMenu (m_recentFilesMenu);	// Open Recent
-	addMenuAction ("save");				// Save
-	addMenuAction ("saveAs");				// Save As
-	menu->addSeparator ();					// -------
-	addMenuAction ("settings");			// Settings
-	addMenuAction ("setLDrawPath");		// Set LDraw Path
-	menu->addSeparator ();					// -------
-	addMenuAction ("exit");				// Exit
-	
-	// View menu
-	initMenu ("&View");
-	addMenuAction ("resetView");			// Reset View
-	addMenuAction ("axes");				// Draw Axes
-	menu->addSeparator ();					// -----
-	addMenuAction ("screencap");			// Screencap Part
-	addMenuAction ("showHistory");		// Edit History
-	
-	// Insert menu
-	initMenu ("&Insert");
-	addMenuAction ("insertFrom");			// Insert from File
-	addMenuAction ("insertRaw");			// Insert Raw
-	menu->addSeparator ();					// -------
-	addMenuAction ("newSubfile");			// New Subfile
-	addMenuAction ("newLine");			// New Line
-	addMenuAction ("newTriangle");		// New Triangle
-	addMenuAction ("newQuad");				// New Quad
-	addMenuAction ("newCondLine");		// New Conditional Line
-	addMenuAction ("newComment");			// New Comment
-	addMenuAction ("newBFC");				// New BFC Statment
-	addMenuAction ("newVertex");			// New Vertex
-	addMenuAction ("newRadial");			// New Radial
-	menu->addSeparator ();					// -----
-	addMenuAction ("beginDraw");			// Begin Drawing
-	addMenuAction ("doneDraw");			// Cancel Drawing
-	addMenuAction ("cancelDraw");			// Done Drawing
-	
-	// Edit menu
-	initMenu ("&Edit");
-	addMenuAction ("undo");				// Undo
-	addMenuAction ("redo");				// Redo
-	menu->addSeparator ();					// -----
-	addMenuAction ("cut");					// Cut
-	addMenuAction ("copy");				// Copy
-	addMenuAction ("paste");				// Paste
-	addMenuAction ("del");					// Delete
-	menu->addSeparator ();					// -----
-	
-	addMenuAction ("selectAll");			// Select All
-	addMenuAction ("selectByColor");		// Select by Color
-	addMenuAction ("selectByType");		// Select by Type
-	
-	initMenu ("&Tools");
-	addMenuAction ("setColor");			// Set Color
-	addMenuAction ("invert");				// Invert
-	addMenuAction ("inlineContents");		// Inline
-	addMenuAction ("deepInline");			// Deep Inline
-	addMenuAction ("splitQuads");			// Split Quads
-	addMenuAction ("setContents");		// Set Contents
-	addMenuAction ("makeBorders");		// Make Borders
-	addMenuAction ("makeCornerVerts");	// Make Corner Vertices
-	addMenuAction ("roundCoords");		// Round Coordinates
-	addMenuAction ("uncolorize");			// Uncolorize
-	addMenuAction ("visibility");			// Toggle Visibility
-	
-	// Move menu
-	initMenu ("&Move");
-	addMenuAction ("moveUp");				// Move Up
-	addMenuAction ("moveDown");			// Move Down
-	menu->addSeparator ();					// -----
-	addMenuAction ("gridCoarse");			// Coarse Grid
-	addMenuAction ("gridMedium");			// Medium Grid
-	addMenuAction ("gridFine");			// Fine Grid
-	menu->addSeparator ();					// -----
-	addMenuAction ("moveXPos");			// Move +X
-	addMenuAction ("moveXNeg");			// Move -X
-	addMenuAction ("moveYPos");			// Move +Y
-	addMenuAction ("moveYNeg");			// Move -Y
-	addMenuAction ("moveZPos");			// Move +Z
-	addMenuAction ("moveZNeg");			// Move -Z
-	menu->addSeparator ();					// -----
-	addMenuAction ("rotateXPos");			// Rotate +X
-	addMenuAction ("rotateXNeg");			// Rotate -X
-	addMenuAction ("rotateYPos");			// Rotate +Y
-	addMenuAction ("rotateYNeg");			// Rotate -Y
-	addMenuAction ("rotateZPos");			// Rotate +Z
-	addMenuAction ("rotateZNeg");			// Rotate -Z
-	
-	initMenu ("E&xternal Programs");
-	addMenuAction ("ytruder");
-	addMenuAction ("rectifier");
-	addMenuAction ("intersector");
-	
-	// Help menu
-	initMenu ("&Help");
-	addMenuAction ("help");				// Help
-	menu->addSeparator ();					// -----
-	addMenuAction ("about");				// About
-	addMenuAction ("aboutQt");				// About Qt
-}
-
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::updateRecentFilesMenu () {
-	// First, clear any items in the recent files menu
-	for (QAction* recent : m_recentFiles)
-		delete recent;
-	m_recentFiles.clear ();
-	
-	std::vector<str> files = io_recentfiles.value / "@";
-	for (long i = files.size() - 1; i >= 0; --i) {
-		str file = files[i];
-		
-		QAction* recent = new QAction (getIcon ("open-recent"), file, this);
-		
-		connect (recent, SIGNAL (triggered ()), this, SLOT (slot_recentFile ()));
-		m_recentFilesMenu->addAction (recent);
-		m_recentFiles.push_back (recent);
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-static QToolBar* g_CurrentToolBar;
-static Qt::ToolBarArea g_ToolBarArea = Qt::TopToolBarArea;
-
-void ForgeWindow::initSingleToolBar (const char* name) {
-	QToolBar* toolbar = new QToolBar (name);
-	addToolBar (g_ToolBarArea, toolbar);
-	m_toolBars.push_back (toolbar);
-	
-	g_CurrentToolBar = toolbar;
-}
-
-// =============================================================================
-void ForgeWindow::addToolBarAction (const char* name) {
-	g_CurrentToolBar->addAction (findAction (name));
-}
-
-// =============================================================================
-void ForgeWindow::createToolbars () {
-	initSingleToolBar ("File");
-	addToolBarAction ("newFile");
-	addToolBarAction ("open");
-	addToolBarAction ("save");
-	addToolBarAction ("saveAs");
-	
-	// ==========================================
-	initSingleToolBar ("Insert");
-	addToolBarAction ("newSubfile");
-	addToolBarAction ("newLine");
-	addToolBarAction ("newTriangle");
-	addToolBarAction ("newQuad");
-	addToolBarAction ("newCondLine");
-	addToolBarAction ("newComment");
-	addToolBarAction ("newBFC");
-	addToolBarAction ("newVertex");
-	addToolBarAction ("newRadial");
-	
-	// ==========================================
-	initSingleToolBar ("Edit");
-	addToolBarAction ("undo");
-	addToolBarAction ("redo");
-	addToolBarAction ("cut");
-	addToolBarAction ("copy");
-	addToolBarAction ("paste");
-	addToolBarAction ("del");
-	
-	// ==========================================
-	initSingleToolBar ("Select");
-	addToolBarAction ("selectAll");
-	addToolBarAction ("selectByColor");
-	addToolBarAction ("selectByType");
-	
-	addToolBarBreak (Qt::TopToolBarArea);
-	
-	// ==========================================
-	initSingleToolBar ("Move");
-	addToolBarAction ("moveUp");
-	addToolBarAction ("moveDown");
-	addToolBarAction ("moveXPos");
-	addToolBarAction ("moveXNeg");
-	addToolBarAction ("moveYPos");
-	addToolBarAction ("moveYNeg");
-	addToolBarAction ("moveZPos");
-	addToolBarAction ("moveZNeg");
-	
-	// ==========================================
-	initSingleToolBar ("Rotate");
-	addToolBarAction ("rotateXPos");
-	addToolBarAction ("rotateXNeg");
-	addToolBarAction ("rotateYPos");
-	addToolBarAction ("rotateYNeg");
-	addToolBarAction ("rotateZPos");
-	addToolBarAction ("rotateZNeg");
-	
-	// ==========================================
-	// Grid toolbar
-	initSingleToolBar ("Grids");
-	addToolBarAction ("gridCoarse");
-	addToolBarAction ("gridMedium");
-	addToolBarAction ("gridFine");
-	addToolBarBreak (Qt::TopToolBarArea);
-	
-	// ==========================================
-	initSingleToolBar ("View");
-	addToolBarAction ("axes");
-	
-	// ==========================================
-	// Color toolbar
-	m_colorToolBar = new QToolBar ("Quick Colors");
-	addToolBar (Qt::RightToolBarArea, m_colorToolBar);
-	
-	// ==========================================
-	// Left area toolbars
-	//g_ToolBarArea = Qt::LeftToolBarArea;
-	initSingleToolBar ("Tools");
-	addToolBarAction ("setColor");
-	addToolBarAction ("invert");
-	addToolBarAction ("inlineContents");
-	addToolBarAction ("deepInline");
-	addToolBarAction ("splitQuads");
-	addToolBarAction ("setContents");
-	addToolBarAction ("makeBorders");
-	addToolBarAction ("makeCornerVerts");
-	addToolBarAction ("roundCoords");
-	addToolBarAction ("screencap");
-	addToolBarAction ("uncolorize");
-	addToolBarAction ("visibility");
-	
-	initSingleToolBar ("External Programs");
-	addToolBarAction ("ytruder");
-	addToolBarAction ("rectifier");
-	addToolBarAction ("intersector");
-	updateToolBars ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-std::vector<quickColorMetaEntry> parseQuickColorMeta () {
-	std::vector<quickColorMetaEntry> meta;
-	
-	for (str colorname : gui_colortoolbar.value / ":") {
-		if (colorname == "|") {
-			meta.push_back ({null, null, true});
-		} else {
-			color* col = getColor (atoi (colorname));
-			assert (col != null);
-			meta.push_back ({col, null, false});
-		}
-	}
-	
-	return meta;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::updateToolBars () {
-	for (QToolBar* bar : m_toolBars)
-		bar->setIconSize (QSize (gui_toolbar_iconsize, gui_toolbar_iconsize));
-	
-	// Update the quick color toolbar.
-	for (QPushButton* btn : m_colorButtons)
-		delete btn;
-	
-	m_colorButtons.clear ();
-	
-	// Clear the toolbar to remove separators
-	m_colorToolBar->clear ();
-	
-	for (quickColorMetaEntry& entry : m_colorMeta) {
-		if (entry.bSeparator)
-			m_colorToolBar->addSeparator ();
-		else {
-			QPushButton* colorButton = new QPushButton;
-			colorButton->setAutoFillBackground (true);
-			colorButton->setStyleSheet (fmt ("background-color: %s", entry.col->zColorString.chars()));
-			colorButton->setToolTip (entry.col->zName);
-			
-			connect (colorButton, SIGNAL (clicked ()), this, SLOT (slot_quickColor ()));
-			m_colorToolBar->addWidget (colorButton);
-			m_colorButtons.push_back (colorButton);
-			
-			entry.btn = colorButton;
-		}
-	}
-	
-	updateGridToolBar ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::updateGridToolBar () {
-	// Ensure that the current grid - and only the current grid - is selected.
-	findAction ("gridCoarse")->setChecked (grid == Grid::Coarse);
-	findAction ("gridMedium")->setChecked (grid == Grid::Medium);
-	findAction ("gridFine")->setChecked (grid == Grid::Fine);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::setTitle () {
-	str title = APPNAME " v";
-	title += versionString;
-	
-	// Append our current file if we have one
-	if (g_curfile) {
-		title += fmt (": %s", basename (g_curfile->m_filename.chars()));
-		
-		if (g_curfile->m_objs.size() > 0 &&
-			g_curfile->m_objs[0]->getType() == LDObject::Comment)
-		{
-			// Append title
-			LDComment* comm = static_cast<LDComment*> (g_curfile->m_objs[0]);
-			title += fmt (": %s", comm->text.chars());
-		}
-		
-		if (History::pos () != g_curfile->savePos)
-			title += '*';
-	}
-	
-	setWindowTitle (title);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::slot_action () {
-	// Get the action that triggered this slot.
-	QAction* qAct = static_cast<QAction*> (sender ());
-	
-	// Find the meta for the action.
-	actionmeta* pMeta = null;
-	
-	for (actionmeta meta : g_ActionMeta) {
-		if (*meta.qAct == qAct) {
-			pMeta = &meta;
-			break;
-		}
-	}
-	
-	if (!pMeta) {
-		logf (LOG_Warning, "unknown signal sender %p!\n", qAct);
-		return;
-	}
-	
-	// We have the meta, now call the handler.
-	(*pMeta->handler) ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::deleteSelection (vector<ulong>* ulapIndices, std::vector<LDObject*>* papObjects) {
-	if (m_sel.size () == 0)
-		return;
-	
-	std::vector<LDObject*> selCopy = m_sel;
-	
-	// Delete the objects that were being selected
-	for (LDObject* obj : selCopy) {
-		if (papObjects && ulapIndices) {
-			papObjects->push_back (obj->clone ());
-			ulapIndices->push_back (obj->getIndex (g_curfile));
-		}
-		
-		g_curfile->forgetObject (obj);
-		delete obj;
-	}
-	
-	refresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::buildObjList () {
-	if (!g_curfile)
-		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_bSelectionLocked = true;
-	
-	m_objList->clear ();
-	
-	for (LDObject* obj : g_curfile->m_objs) {
-		str descr;
-		switch (obj->getType ()) {
-		case LDObject::Comment:
-			descr = static_cast<LDComment*> (obj)->text.chars();
-			
-			// Remove leading whitespace
-			while (~descr && descr[0] == ' ')
-				descr -= -1;
-			break;
-		
-		case LDObject::Empty:
-			break; // leave it empty
-		
-		case LDObject::Line:
-			{
-				LDLine* line = static_cast<LDLine*> (obj);
-				descr.format ("%s, %s",
-					line->vaCoords[0].stringRep (true).chars(),
-					line->vaCoords[1].stringRep (true).chars());
-			}
-			break;
-		
-		case LDObject::Triangle:
-			{
-				LDTriangle* triangle = static_cast<LDTriangle*> (obj);
-				descr.format ("%s, %s, %s",
-					triangle->vaCoords[0].stringRep (true).chars(),
-					triangle->vaCoords[1].stringRep (true).chars(),
-					triangle->vaCoords[2].stringRep (true).chars());
-			}
-			break;
-		
-		case LDObject::Quad:
-			{
-				LDQuad* quad = static_cast<LDQuad*> (obj);
-				descr.format ("%s, %s, %s, %s",
-					quad->vaCoords[0].stringRep (true).chars(),
-					quad->vaCoords[1].stringRep (true).chars(),
-					quad->vaCoords[2].stringRep (true).chars(),
-					quad->vaCoords[3].stringRep (true).chars());
-			}
-			break;
-		
-		case LDObject::CondLine:
-			{
-				LDCondLine* line = static_cast<LDCondLine*> (obj);
-				descr.format ("%s, %s, %s, %s",
-					line->vaCoords[0].stringRep (true).chars(),
-					line->vaCoords[1].stringRep (true).chars(),
-					line->vaCoords[2].stringRep (true).chars(),
-					line->vaCoords[3].stringRep (true).chars());
-			}
-			break;
-		
-		case LDObject::Gibberish:
-			descr.format ("ERROR: %s",
-				static_cast<LDGibberish*> (obj)->zContents.chars());
-			break;
-		
-		case LDObject::Vertex:
-			descr.format ("%s", static_cast<LDVertex*> (obj)->vPosition.stringRep (true).chars());
-			break;
-		
-		case LDObject::Subfile:
-			{
-				LDSubfile* ref = static_cast<LDSubfile*> (obj);
-				
-				descr.format ("%s %s, (",
-					ref->zFileName.chars(), ref->vPosition.stringRep (true).chars());
-				
-				for (short i = 0; i < 9; ++i)
-					descr.appendformat ("%s%s",
-						ftoa (ref->mMatrix[i]).chars(),
-						(i != 8) ? " " : "");
-				
-				descr += ')';
-			}
-			break;
-		
-		case LDObject::BFC:
-			{
-				LDBFC* bfc = static_cast<LDBFC*> (obj);
-				descr = LDBFC::statements[bfc->type];
-			}
-			break;
-		
-		case LDObject::Radial:
-			{
-				LDRadial* pRad = static_cast<LDRadial*> (obj);
-				descr.format ("%d / %d %s", pRad->dSegments, pRad->dDivisions, pRad->radialTypeName());
-				
-				if (pRad->eRadialType == LDRadial::Ring || pRad->eRadialType == LDRadial::Cone)
-					descr.appendformat (" %d", pRad->dRingNum);
-				
-				descr.appendformat (" %s", pRad->vPosition.stringRep (true).chars ());
-			}
-			break;
-		
-		default:
-			descr = g_saObjTypeNames[obj->getType ()];
-			break;
-		}
-		
-		// Put it into brackets if it's hidden
-		if (obj->hidden ()) {
-			str copy = descr.chars ();
-			descr.format ("[[ %s ]]", copy.chars ());
-		}
-		
-		QListWidgetItem* item = new QListWidgetItem (descr.chars());
-		item->setIcon (getIcon (g_saObjTypeIcons[obj->getType ()]));
-		
-		// Color gibberish orange on red so it stands out.
-		if (obj->getType() == LDObject::Gibberish) {
-			item->setBackground (QColor ("#AA0000"));
-			item->setForeground (QColor ("#FFAA00"));
-		} else if (lv_colorize && obj->isColored () &&
-			obj->dColor != maincolor && obj->dColor != edgecolor)
-		{
-			// If the object isn't in the main or edge color, draw this
-			// list entry in said color.
-			color* col = getColor (obj->dColor);
-			if (col)
-				item->setForeground (col->qColor);
-		}
-		
-		obj->qObjListEntry = item;
-		m_objList->insertItem (m_objList->count (), item);
-	}
-	
-	g_bSelectionLocked = false;
-	updateSelection ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::scrollToSelection () {
-	if (m_sel.size() == 0)
-		return;
-	
-	LDObject* obj = m_sel[m_sel.size () - 1];
-	m_objList->scrollToItem (obj->qObjListEntry);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::slot_selectionChanged () {
-	if (g_bSelectionLocked == true || g_curfile == null)
-		return;
-	
-	/*
-	// If the selection isn't 1 exact, disable setting contents
-	findAction ("setContents")->setEnabled (qObjList->selectedItems().size() == 1);
-	
-	// If we have no selection, disable splitting quads
-	findAction ("splitQuads")->setEnabled (qObjList->selectedItems().size() > 0);
-	*/
-	
-	// Update the shared selection array, though don't do this if this was
-	// called during GL picking, in which case the GL renderer takes care
-	// of the selection.
-	if (m_renderer->picking ())
-		return;
-	
-	std::vector<LDObject*> priorSelection = m_sel;
-	
-	// Get the objects from the object list selection
-	m_sel.clear ();	
-	const QList<QListWidgetItem*> items = m_objList->selectedItems ();
-	
-	for (LDObject* obj : g_curfile->m_objs)
-	for (QListWidgetItem* qItem : items) {
-		if (qItem == obj->qObjListEntry) {
-			m_sel.push_back (obj);
-			break;
-		}
-	}
-	
-	// Update the GL renderer
-	for (LDObject* obj : m_sel)
-		m_renderer->recompileObject (obj);
-	
-	for (LDObject* obj : priorSelection)
-		m_renderer->recompileObject (obj);
-	
-	m_renderer->updateSelFlash ();
-	m_renderer->refresh ();
-}
-
-// =============================================================================
-void ForgeWindow::slot_recentFile () {
-	QAction* qAct = static_cast<QAction*> (sender ());
-	openMainFile (qAct->text ());
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::slot_quickColor () {
-	QPushButton* button = static_cast<QPushButton*> (sender ());
-	color* col = null;
-	
-	for (quickColorMetaEntry entry : m_colorMeta) {
-		if (entry.btn == button) {
-			col = entry.col;
-			break;
-		}
-	}
-	
-	if (col == null)
-		return;
-	
-	std::vector<ulong> indices;
-	std::vector<short> colors;
-	short newColor = col->index ();
-	
-	for (LDObject* obj : m_sel) {
-		if (obj->dColor == -1)
-			continue; // uncolored object
-		
-		indices.push_back (obj->getIndex (g_curfile));
-		colors.push_back (obj->dColor);
-		
-		obj->dColor = newColor;
-	}
-	
-	History::addEntry (new SetColorHistory (indices, colors, newColor));
-	refresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-ulong ForgeWindow::getInsertionPoint () {
-	if (m_sel.size () > 0) {
-		// If we have a selection, put the item after it.
-		return (m_sel[m_sel.size() - 1]->getIndex (g_curfile)) + 1;
-	}
-	
-	// Otherwise place the object at the end.
-	return g_curfile->m_objs.size();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::refresh () {
-	buildObjList ();
-	m_renderer->hardRefresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::updateSelection () {
-	g_bSelectionLocked = true;
-	
-	m_objList->clearSelection ();
-	for (LDObject* obj : m_sel)
-		obj->qObjListEntry->setSelected (true);
-	
-	g_bSelectionLocked = false;
-	slot_selectionChanged ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-bool ForgeWindow::isSelected (LDObject* obj) {
-	LDObject* needle = obj->topLevelParent ();
-	
-	if (needle == null)
-		needle = obj;
-	
-	for (LDObject* hay : m_sel)
-		if (hay == needle)
-			return true;
-	
-	return false;
-}
-
-short ForgeWindow::getSelectedColor() {
-	short result = -1;
-	
-	for (LDObject* obj : m_sel) {
-		if (obj->dColor == -1)
-			continue; // doesn't use color
-		
-		if (result != -1 && obj->dColor != result)
-			return -1; // No consensus in object color
-		
-		if (result == -1)
-			result = obj->dColor;
-	}
-	
-	return result;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-LDObject::Type ForgeWindow::uniformSelectedType () {
-	LDObject::Type eResult = LDObject::Unidentified;
-	
-	for (LDObject* obj : m_sel) {
-		if (eResult != LDObject::Unidentified && obj->dColor != eResult)
-			return LDObject::Unidentified;
-		
-		if (eResult == LDObject::Unidentified)
-			eResult = obj->getType ();
-	}
-	
-	return eResult;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::closeEvent (QCloseEvent* ev) {
-	// Check whether it's safe to close all files.
-	for (OpenFile* f : g_loadedFiles) {
-		if (!f->safeToClose ()) {
-			ev->ignore ();
-			return;
-		}
-	}
-	
-	// Save the configuration before leaving so that, for instance, grid choice
-	// is preserved across instances.
-	config::save ();
-	
-	ev->accept ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ForgeWindow::spawnContextMenu (const QPoint pos) {
-	const bool single = (g_win->sel ().size () == 1);
-	LDObject* singleObj = (single) ? g_win->sel ()[0] : null;
-	
-	QMenu* contextMenu = new QMenu;
-	
-	if (single && singleObj->getType () != LDObject::Empty) {
-		contextMenu->addAction (findAction ("editObject"));
-		contextMenu->addSeparator ();
-	}
-	
-	contextMenu->addAction (findAction ("cut"));
-	contextMenu->addAction (findAction ("copy"));
-	contextMenu->addAction (findAction ("paste"));
-	contextMenu->addAction (findAction ("del"));
-	contextMenu->addSeparator ();
-	contextMenu->addAction (findAction ("setColor"));
-	if (single)
-		contextMenu->addAction (findAction ("setContents"));
-	contextMenu->addAction (findAction ("makeBorders"));
-	
-	contextMenu->exec (pos);
-}
-
-// ========================================================================================================================================
-DelHistory* ForgeWindow::deleteObjVector (const std::vector<LDObject*> objs) {
-	vector<ulong> indices;
-	vector<LDObject*> cache;
-	
-	for (LDObject* obj : objs) {
-		indices.push_back (obj->getIndex (g_curfile));
-		cache.push_back (obj->clone ());
-		
-		g_curfile->forgetObject (obj);
-		delete obj;
-	}
-	
-	if (indices.size () > 0)
-		return new DelHistory (indices, cache);
-	
-	return null;
-}
-
-// ========================================================================================================================================
-DelHistory* ForgeWindow::deleteSelection () {
-	return deleteObjVector (sel ());
-}
-
-// ========================================================================================================================================
-DelHistory* ForgeWindow::deleteByColor (const short colnum) {
-	vector<LDObject*> objs;
-	for (LDObject* obj : g_curfile->m_objs) {
-		if (!obj->isColored () || obj->dColor != colnum)
-			continue;
-		
-		objs.push_back (obj);
-	}
-	
-	return deleteObjVector (objs);
-}
-
-// ========================================================================================================================================
-void ObjectList::contextMenuEvent (QContextMenuEvent* ev) {
-	g_win->spawnContextMenu (ev->globalPos ());
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-QPixmap getIcon (const char* iconName) {
-	return (QPixmap (fmt (":/icons/%s.png", iconName)));
-}
-
-// =============================================================================
-bool confirm (str msg) {
-	return confirm ("Confirm", msg);
-}
-
-bool confirm (str title, str msg) {
-	return QMessageBox::question (g_win, title, msg,
-		(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes;
-}
-
-// =============================================================================
-void critical (str msg) {
-	QMessageBox::critical (g_win, "Error", msg,
-		(QMessageBox::Close), QMessageBox::Close);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-// Print to message log
-// TODO: I don't think that the message log being a widget in the window
-// is a very good idea... maybe this should log into the renderer? Or into
-// another dialog that can be popped up?
-void ForgeWindow::logVA (LogType type, const char* fmtstr, va_list va) {
-	(void) type;
-	(void) fmtstr;
-	(void) va;
-}
-
-// =============================================================================
-QAction* findAction (str name) {
-	for (actionmeta& meta : g_ActionMeta)
-		if (name == meta.name)
-			return *meta.qAct;
-	
-	fprintf (stderr, "%s: couldn't find action named `%s'!\n", __func__, name.chars ());
-	assert (false);
-	return null;
-}
-
-// =============================================================================
-void makeColorSelector (QComboBox* box) {
-	std::map<short, ulong> counts;
-	
-	for (LDObject* obj : g_curfile->m_objs) {
-		if (!obj->isColored ())
-			continue;
-		
-		if (counts.find (obj->dColor) == counts.end ())
-			counts[obj->dColor] = 1;
-		else
-			counts[obj->dColor]++;
-	}
-	
-	box->clear ();
-	ulong row = 0;
-	for (const auto& pair : counts) {
-		const ushort size = 16;
-		color* col = getColor (pair.first);
-		assert (col != null);
-		
-		// Paint an icon for this color
-		QIcon ico;
-		QImage img (size, size, QImage::Format_ARGB32);
-		QPainter paint (&img);
-		paint.fillRect (QRect (0, 0, size, size), Qt::black);
-		paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8));
-		paint.fillRect (QRect (1, 1, size - 2, size - 2), QColor (col->qColor));
-		ico = QIcon (QPixmap::fromImage (img));
-		
-		box->addItem (ico, fmt ("[%d] %s (%lu object%s)",
-			pair.first, col->zName.chars (), pair.second, PLURAL (pair.second)));
-		box->setItemData (row, pair.first);
-		
-		++row;
-	}
-}
\ No newline at end of file
--- a/gui.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,253 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef GUI_H
-#define GUI_H
-
-#include <QMainWindow>
-#include <QMenu>
-#include <QToolBar>
-#include <QAction>
-#include <QToolBar>
-#include <QTextEdit>
-#include <qpushbutton.h>
-#include <qlistwidget.h>
-#include <qlabel.h>
-#include <qboxlayout.h>
-#include "gldraw.h"
-#include "config.h"
-
-class QComboBox;
-class ForgeWindow;
-class color;
-class QSplitter;
-class DelHistory;
-
-// Stuff for dialogs
-#define IMPLEMENT_DIALOG_BUTTONS \
-	bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); \
-	connect (bbx_buttons, SIGNAL (accepted ()), this, SLOT (accept ())); \
-	connect (bbx_buttons, SIGNAL (rejected ()), this, SLOT (reject ())); \
-
-// =============================================================================
-// Metadata for actions
-typedef struct {
-	QAction** const qAct;
-	keyseqconfig* const conf;
-	const char* const name, *sDisplayName, *sIconName, *sDescription;
-	void (*const handler) ();
-} actionmeta;
-
-extern vector<actionmeta> g_ActionMeta;
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-#define MAKE_ACTION(NAME, DISPLAYNAME, ICONNAME, DESCR, DEFSHORTCUT) \
-	QAction* ACTION (NAME); \
-	cfg (keyseq, key_##NAME, DEFSHORTCUT); \
-	static void actionHandler_##NAME (); \
-	static ActionAdder ActionAdderInstance_##NAME (&ACTION(NAME), DISPLAYNAME, \
-		ICONNAME, DESCR, &key_##NAME, actionHandler_##NAME, #NAME); \
-	static void actionHandler_##NAME ()
-
-#define EXTERN_ACTION(NAME) extern QAction* ACTION (NAME);
-#define ACTION(N) LDForgeAction_##N
-
-// Convenience macros for key sequences.
-#define KEY(N) (Qt::Key_##N)
-#define CTRL(N) (Qt::CTRL | Qt::Key_##N)
-#define SHIFT(N) (Qt::SHIFT | Qt::Key_##N)
-#define CTRL_SHIFT(N) (Qt::CTRL | Qt::SHIFT | Qt::Key_##N)
-
-// =============================================================================
-typedef struct {
-	color* col;
-	QPushButton* btn;
-	bool bSeparator;
-} quickColorMetaEntry;
-
-// =============================================================================
-// ActionAdder
-//
-// The ACTION macro expands into - among other stuff - into an instance of this.
-// This class' constructor creates meta for the newly defined action and stores
-// it in g_ActionMeta. It is not supposed to be used directly!
-// =============================================================================
-class ActionAdder {
-public:
-	ActionAdder (QAction** qAct, const char* sDisplayName, const char* sIconName,
-		const char* sDescription, keyseqconfig* conf, void (*const handler) (),
-		const char* name)
-	{
-		actionmeta meta = {qAct, conf, name, sDisplayName, sIconName, sDescription, handler};
-		g_ActionMeta.push_back (meta);
-	}
-};
-
-// =============================================================================
-// ObjectList
-// 
-// Object list class for ForgeWindow
-// =============================================================================
-class ObjectList : public QListWidget {
-	Q_OBJECT
-	
-protected:
-	void contextMenuEvent (QContextMenuEvent* ev);
-};
-
-// =============================================================================
-// ForgeWindow
-// 
-// The one main GUI class. Hosts the renderer, object list, message log. Contains
-// slot_action, which is what all actions connect to. Manages menus and toolbars.
-// Large and in charge.
-// =============================================================================
-class ForgeWindow : public QMainWindow {
-	Q_OBJECT
-	
-public:
-	ForgeWindow ();
-	void buildObjList ();
-	void setTitle ();
-	void refresh ();
-	ulong getInsertionPoint ();
-	void deleteSelection (vector<ulong>* ulapIndices, std::vector<LDObject*>* papObjects);
-	void updateToolBars ();
-	void updateRecentFilesMenu ();
-	void updateSelection ();
-	void updateGridToolBar ();
-	bool isSelected (LDObject* obj);
-	short getSelectedColor();
-	LDObject::Type uniformSelectedType ();
-	void scrollToSelection ();
-	void spawnContextMenu (const QPoint pos);
-	DelHistory* deleteObjVector (const std::vector<LDObject*> objs);
-	DelHistory* deleteSelection ();
-	DelHistory* deleteByColor (const short colnum);
-	GLRenderer* R () { return m_renderer; }
-	std::vector<LDObject*>& sel () { return m_sel; }
-	void setQuickColorMeta (std::vector<quickColorMetaEntry>& quickColorMeta) {
-		m_colorMeta = quickColorMeta;
-	}
-	
-protected:
-	void closeEvent (QCloseEvent* ev);
-	void logVA (LogType eType, const char* fmtstr, va_list va);
-	
-	friend void logf (const char* fmt, ...);
-	friend void logf (LogType eType, const char* fmt, ...);
-	friend void warnf (const char* fmt, ...);
-	friend void infof (const char* fmt, ...);
-	friend void succf (const char* fmt, ...);
-	friend void errf (const char* fmt, ...);
-	friend void devf (const char* fmt, ...);
-	
-private:
-	GLRenderer* m_renderer;
-	ObjectList* m_objList;
-	QMenu* m_recentFilesMenu;
-	QSplitter* m_splitter;
-	str m_msglogHTML;
-	QToolBar* m_colorToolBar;
-	std::vector<QToolBar*> m_toolBars;
-	std::vector<LDObject*> m_sel;
-	std::vector<quickColorMetaEntry> m_colorMeta;
-	std::vector<QPushButton*> m_colorButtons;
-	std::vector<QAction*> m_recentFiles;
-	
-	void createMenuActions ();
-	void createMenus ();
-	void createToolbars ();
-	void initSingleToolBar (const char* name);
-	void addToolBarAction (const char* name);
-	
-	QMenu* initMenu (const char* name);
-	void addMenuAction (const char* name);
-
-private slots:
-	void slot_selectionChanged ();
-	void slot_action ();
-	void slot_recentFile ();
-	void slot_quickColor ();
-	void slot_lastSecondCleanup ();
-};
-
-// =============================================================================
-// LabeledWidget
-//
-// Convenience class for a widget with a label beside it.
-// =============================================================================
-template<class R> class LabeledWidget : public QWidget {
-public:
-	explicit LabeledWidget (const char* labelstr, QWidget* parent = null) : QWidget (parent) {
-		m_widget = new R (this);
-		commonInit (labelstr);
-	}
-	
-	explicit LabeledWidget (const char* labelstr, R* widget, QWidget* parent = null) :
-		QWidget (parent), m_widget (widget)
-	{
-		commonInit (labelstr);
-	}
-	
-	explicit LabeledWidget (QWidget* parent = 0, Qt::WindowFlags f = 0) {
-		m_widget = new R (this);
-		commonInit ("");
-	}
-	
-	R* widget () const { return m_widget; }
-	R* w () const { return m_widget; }
-	QLabel* label () const { return m_label; }
-	QLabel* l () const { return m_label; }
-	void setWidget (R* widget) { m_widget = widget; }
-	void setLabel (QLabel* label) { m_label = label; }
-	operator R* () { return m_widget; }
-	
-private:
-	Q_DISABLE_COPY (LabeledWidget<R>)
-	
-	void commonInit (const char* labelstr) {
-		m_label = new QLabel (labelstr, this);
-		m_layout = new QHBoxLayout;
-		m_layout->addWidget (m_label);
-		m_layout->addWidget (m_widget);
-		setLayout (m_layout);
-	}
-	
-	R* m_widget;
-	QLabel* m_label;
-	QHBoxLayout* m_layout;
-};
-
-// -----------------------------------------------------------------------------
-// Other GUI-related stuff not directly part of ForgeWindow:
-QPixmap getIcon (const char* sIconName);
-std::vector<quickColorMetaEntry> parseQuickColorMeta ();
-bool confirm (str title, str msg);
-bool confirm (str msg);
-void critical (str msg);
-QAction* findAction (str name);
-void makeColorSelector (QComboBox* box);
-
-// -----------------------------------------------------------------------------
-// Pointer to the instance of ForgeWindow.
-extern ForgeWindow* g_win;
-
-#endif // GUI_H
\ No newline at end of file
--- a/gui_actions.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,419 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <qfiledialog.h>
-#include <qmessagebox.h>
-#include "gui.h"
-#include "file.h"
-#include "history.h"
-#include "zz_newPartDialog.h"
-#include "zz_configDialog.h"
-#include "zz_addObjectDialog.h"
-#include "zz_aboutDialog.h"
-#include "misc.h"
-#include "zz_ldrawPathDialog.h"
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (newFile, "&New", "brick", "Create a new part model.", CTRL (N)) {
-	NewPartDialog::StaticDialog ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (open, "&Open", "file-open", "Load a part model from a file.", CTRL (O)) {
-	str zName;
-	zName += QFileDialog::getOpenFileName (g_win, "Open File",
-		"", "LDraw files (*.dat *.ldr)");
-	
-	if (~zName)
-		openMainFile (zName);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void doSave (bool saveAs) {
-	str path = g_curfile->m_filename;
-	
-	if (~path == 0 || saveAs) {
-		path = QFileDialog::getSaveFileName (g_win, "Save As",
-		"", "LDraw files (*.dat *.ldr)");
-		
-		if (~path == 0) {
-			// User didn't give a file name. This happens if the user cancelled
-			// saving in the save file dialog. Abort.
-			return;
-		}
-	}
-	
-	if (g_curfile->save (path)) {
-		g_curfile->m_filename = path;
-		g_win->setTitle ();
-		
-		logf ("Saved successfully to %s\n", path.chars ());
-	} else {
-		setlocale (LC_ALL, "C");
-		
-		// Tell the user the save failed, and give the option for saving as with it.
-		QMessageBox dlg (QMessageBox::Critical, "Save Failure",
-			fmt ("Failed to save to %s\nReason: %s", path.chars(), strerror (g_curfile->lastError)),
-			QMessageBox::Close, g_win);
-		
-		QPushButton* saveAsBtn = new QPushButton ("Save As");
-		saveAsBtn->setIcon (getIcon ("file-save-as"));
-		dlg.addButton (saveAsBtn, QMessageBox::ActionRole);
-		dlg.setDefaultButton (QMessageBox::Close);
-		dlg.exec ();
-		
-		if (dlg.clickedButton () == saveAsBtn)
-			doSave (true); // yay recursion!
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (save, "&Save", "file-save", "Save the part model.", CTRL (S)) {
-	doSave (false);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (saveAs, "Save &As", "file-save-as", "Save the part model to a specific file.", CTRL_SHIFT (S)) {
-	doSave (true);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (settings, "Settin&gs", "settings", "Edit the settings of " APPNAME ".", (0)) {
-	ConfigDialog::staticDialog ();
-}
-
-MAKE_ACTION (setLDrawPath, "Set LDraw Path", "settings", "Change the LDraw directory path.", (0)) {
-	LDrawPathDialog dlg (true);
-	dlg.exec ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (exit, "&Exit", "exit", "Close " APPNAME ".", CTRL (Q)) {
-	exit (0);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (newSubfile, "New Subfile", "add-subfile", "Creates a new subfile reference.", 0) {
-	AddObjectDialog::staticDialog (LDObject::Subfile, null);
-}
-
-MAKE_ACTION (newLine, "New Line",  "add-line", "Creates a new line.", 0) {
-	AddObjectDialog::staticDialog (LDObject::Line, null);
-}
-
-MAKE_ACTION (newTriangle, "New Triangle", "add-triangle", "Creates a new triangle.", 0) {
-	AddObjectDialog::staticDialog (LDObject::Triangle, null);
-}
-
-MAKE_ACTION (newQuad, "New Quadrilateral", "add-quad", "Creates a new quadrilateral.", 0) {
-	AddObjectDialog::staticDialog (LDObject::Quad, null);
-}
-
-MAKE_ACTION (newCondLine, "New Conditional Line", "add-condline", "Creates a new conditional line.", 0) {
-	AddObjectDialog::staticDialog (LDObject::CondLine, null);
-}
-
-MAKE_ACTION (newComment, "New Comment", "add-comment", "Creates a new comment.", 0) {
-	AddObjectDialog::staticDialog (LDObject::Comment, null);
-}
-
-MAKE_ACTION (newBFC, "New BFC Statement", "add-bfc", "Creates a new BFC statement.", 0) {
-	AddObjectDialog::staticDialog (LDObject::BFC, null);
-}
-
-MAKE_ACTION (newVertex, "New Vertex", "add-vertex", "Creates a new vertex.", 0) {
-	AddObjectDialog::staticDialog (LDObject::Vertex, null);
-}
-
-MAKE_ACTION (newRadial, "New Radial", "add-radial", "Creates a new radial.", 0) {
-	AddObjectDialog::staticDialog (LDObject::Radial, null);
-}
-
-MAKE_ACTION (editObject, "Edit Object", "edit-object", "Edits this object.", 0) {
-	if (g_win->sel ().size () != 1)
-		return;
-	
-	LDObject* obj = g_win->sel ()[0];
-	AddObjectDialog::staticDialog (obj->getType (), obj);
-}
-
-MAKE_ACTION (help, "Help", "help", "Shows the " APPNAME " help manual.", KEY (F1)) {
-	
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (about, "About " APPNAME, "ldforge",
-	"Shows information about " APPNAME ".", (0))
-{
-	AboutDialog dlg;
-	dlg.exec ();
-}
-
-MAKE_ACTION (aboutQt, "About Qt", "qt", "Shows information about Qt.", (0)) {
-	QMessageBox::aboutQt (g_win);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (selectAll, "Select All", "select-all", "Selects all objects.", CTRL (A)) {
-	g_win->sel ().clear ();
-	
-	for (LDObject* obj : g_curfile->m_objs)
-		g_win->sel ().push_back (obj);
-	
-	g_win->updateSelection ();
-}
-
-// =============================================================================
-MAKE_ACTION (selectByColor, "Select by Color", "select-color",
-	"Select all objects by the given color.", CTRL_SHIFT (A))
-{
-	short dColor = g_win->getSelectedColor ();
-	
-	if (dColor == -1)
-		return; // no consensus on color
-	
-	g_win->sel ().clear ();
-	for (LDObject* obj : g_curfile->m_objs)
-		if (obj->dColor == dColor)
-			g_win->sel ().push_back (obj);
-	
-	g_win->updateSelection ();
-}
-
-// =============================================================================
-MAKE_ACTION (selectByType, "Select by Type", "select-type",
-	"Select all objects by the given type.", (0))
-{
-	if (g_win->sel ().size () == 0)
-		return;
-	
-	LDObject::Type eType = g_win->uniformSelectedType ();
-	
-	if (eType == LDObject::Unidentified)
-		return;
-	
-	// If we're selecting subfile references, the reference filename must also
-	// be uniform.
-	str zRefName;
-	
-	if (eType == LDObject::Subfile) {
-		zRefName = static_cast<LDSubfile*> (g_win->sel ()[0])->zFileName;
-		
-		for (LDObject* pObj : g_win->sel ())
-			if (static_cast<LDSubfile*> (pObj)->zFileName != zRefName)
-				return;
-	}
-	
-	g_win->sel ().clear ();
-	for (LDObject* obj : g_curfile->m_objs) {
-		if (obj->getType() != eType)
-			continue;
-		
-		if (eType == LDObject::Subfile && static_cast<LDSubfile*> (obj)->zFileName != zRefName)
-			continue;
-		
-		g_win->sel ().push_back (obj);
-	}
-	
-	g_win->updateSelection ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (gridCoarse, "Coarse Grid", "grid-coarse", "Set the grid to Coarse", CTRL (1)) {
-	grid = Grid::Coarse;
-	g_win->updateGridToolBar ();
-}
-
-MAKE_ACTION (gridMedium, "Medium Grid", "grid-medium", "Set the grid to Medium", CTRL (2)) {
-	grid = Grid::Medium;
-	g_win->updateGridToolBar ();
-}
-
-MAKE_ACTION (gridFine, "Fine Grid", "grid-fine", "Set the grid to Fine", CTRL (3)) {
-	grid = Grid::Fine;
-	g_win->updateGridToolBar ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (resetView, "Reset View", "reset-view", "Reset view angles, pan and zoom", CTRL (0)) {
-	g_win->R ()->resetAngles ();
-	g_win->R ()->update ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (insertFrom, "Insert from File", "insert-from", "Insert LDraw data from a file.", (0)) {
-	str fname = QFileDialog::getOpenFileName ();
-	ulong idx = g_win->getInsertionPoint ();
-	
-	if (!~fname)
-		return;
-	
-	FILE* fp = fopen (fname, "r");
-	if (!fp) {
-		critical (fmt ("Couldn't open %s\n%s", fname.chars(), strerror (errno)));
-		return;
-	}
-	
-	std::vector<LDObject*> historyCopies;
-	std::vector<ulong> historyIndices;
-	std::vector<LDObject*> objs = loadFileContents (fp, null);
-	
-	g_win->sel ().clear ();
-	
-	for (LDObject* obj : objs) {
-		historyCopies.push_back (obj->clone ());
-		historyIndices.push_back (idx);
-		g_curfile->insertObj (idx, obj);
-		g_win->sel ().push_back (obj);
-		
-		idx++;
-	}
-	
-	if (historyCopies.size() > 0) {
-		History::addEntry (new AddHistory (historyIndices, historyCopies));
-		g_win->refresh ();
-		g_win->scrollToSelection ();
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (insertRaw, "Insert Raw", "insert-raw", "Type in LDraw code to insert.", (0)) {
-	ulong idx = g_win->getInsertionPoint ();
-	
-	QDialog* const dlg = new QDialog;
-	QVBoxLayout* const layout = new QVBoxLayout;
-	QTextEdit* const te_edit = new QTextEdit;
-	QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-	std::vector<LDObject*> historyCopies;
-	std::vector<ulong> historyIndices;
-	
-	layout->addWidget (te_edit);
-	layout->addWidget (bbx_buttons);
-	dlg->setLayout (layout);
-	dlg->setWindowTitle (APPNAME ": Insert Raw");
-	dlg->connect (bbx_buttons, SIGNAL (accepted ()), dlg, SLOT (accept ()));
-	dlg->connect (bbx_buttons, SIGNAL (rejected ()), dlg, SLOT (reject ()));
-	
-	if (dlg->exec () == false)
-		return;
-	
-	g_win->sel ().clear ();
-	
-	for (str line : str (te_edit->toPlainText ()).split ("\n")) {
-		LDObject* obj = parseLine (line);
-		
-		g_curfile->insertObj (idx, obj);
-		historyIndices.push_back (idx);
-		historyCopies.push_back (obj->clone ());
-		g_win->sel ().push_back (obj);
-		idx++;
-	}
-	
-	if (historyCopies.size () > 0) {
-		History::addEntry (new AddHistory (historyIndices, historyCopies));
-		g_win->refresh ();
-		g_win->scrollToSelection ();
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (screencap, "Screencap Part", "screencap", "Save a picture of the model", (0)) {
-	setlocale (LC_ALL, "C");
-	
-	ushort w, h;
-	uchar* imagedata = g_win->R ()->screencap (w, h);
-	
-	// GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well.
-	QImage img = QImage (imagedata, w, h, QImage::Format_ARGB32).rgbSwapped ().mirrored ();
-	
-	str root = basename (g_curfile->m_filename.chars ());
-	if (root.substr (~root - 4, -1) == ".dat")
-		root -= 4;
-	
-	str defaultname = (~root > 0) ? fmt ("%s.png", root.chars ()) : "";
-	str fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname,
-		"PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)");
-	
-	if (~fname > 0 && !img.save (fname))
-		critical (fmt ("Couldn't open %s for writing to save screencap: %s", fname.chars(), strerror (errno)));
-	
-	delete[] imagedata;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-extern_cfg (bool, gl_axes);
-MAKE_ACTION (axes, "Draw Axes", "axes", "Toggles drawing of axes", (0)) {
-	gl_axes = !gl_axes;
-	ACTION (axes)->setChecked (gl_axes);
-	g_win->R ()->update ();
-}
-
-// =============================================================================
-MAKE_ACTION (beginDraw, "Begin Drawing", "draw", "Begin drawing geometry", KEY (Insert)) {
-	g_win->R ()->beginPlaneDraw ();
-}
-
-MAKE_ACTION (cancelDraw, "Cancel Drawing", "draw-cancel", "Cancel drawing geometry", KEY (Escape)) {
-	g_win->R ()->endPlaneDraw (false);
-}
-
-MAKE_ACTION (doneDraw, "Done Drawing", "draw-done", "Done drawing geometry", KEY (Enter)) {
-	g_win->R ()->endPlaneDraw (true);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (visibility, "Toggle Visibility", "visibility", "Toggles visibility/hiding on objects.", (0)) {
-	for (LDObject* obj : g_win->sel ())
-		obj->setHidden (!obj->hidden ());
-	
-	g_win->refresh ();
-}
\ No newline at end of file
--- a/gui_editactions.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,687 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "gui.h"
-#include "common.h"
-#include "file.h"
-#include "history.h"
-#include "zz_colorSelectDialog.h"
-#include "zz_historyDialog.h"
-#include "zz_setContentsDialog.h"
-#include "misc.h"
-#include "bbox.h"
-#include "radiobox.h"
-#include "extprogs.h"
-#include <qspinbox.h>
-#include <qcheckbox.h>
-
-vector<LDObject*> g_Clipboard;
-
-cfg (bool, edit_schemanticinline, false);
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-static bool copyToClipboard () {
-	vector<LDObject*> objs = g_win->sel ();
-	
-	if (objs.size() == 0)
-		return false;
-	
-	// Clear the clipboard first.
-	for (LDObject* obj : g_Clipboard)
-		delete obj;
-	
-	g_Clipboard.clear ();
-	
-	// Now, copy the contents into the clipboard. The objects should be
-	// separate objects so that modifying the existing ones does not affect
-	// the clipboard. Thus, we add clones of the objects to the clipboard, not
-	// the objects themselves.
-	for (ulong i = 0; i < objs.size(); ++i)
-		g_Clipboard.push_back (objs[i]->clone ());
-	
-	return true;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (cut, "Cut", "cut", "Cut the current selection to clipboard.", CTRL (X)) {
-	vector<ulong> ulaIndices;
-	vector<LDObject*> copies;
-	
-	if (!copyToClipboard ())
-		return;
-	
-	g_win->deleteSelection (&ulaIndices, &copies);
-	History::addEntry (new DelHistory (ulaIndices, copies, DelHistory::Cut));
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (copy, "Copy", "copy", "Copy the current selection to clipboard.", CTRL (C)) {
-	copyToClipboard ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (paste, "Paste", "paste", "Paste clipboard contents.", CTRL (V)) {
-	vector<ulong> historyIndices;
-	vector<LDObject*> historyCopies;
-	
-	ulong idx = g_win->getInsertionPoint ();
-	g_win->sel ().clear ();
-	
-	for (LDObject* obj : g_Clipboard) {
-		historyIndices.push_back (idx);
-		historyCopies.push_back (obj->clone ());
-		
-		LDObject* copy = obj->clone ();
-		g_curfile->insertObj (idx, copy);
-		g_win->sel ().push_back (copy);
-	}
-	
-	History::addEntry (new AddHistory (historyIndices, historyCopies, AddHistory::Paste));
-	g_win->refresh ();
-	g_win->scrollToSelection ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (del, "Delete", "delete", "Delete the selection", KEY (Delete)) {
-	vector<ulong> ulaIndices;
-	vector<LDObject*> copies;
-	
-	g_win->deleteSelection (&ulaIndices, &copies);
-	
-	if (copies.size ())
-		History::addEntry (new DelHistory (ulaIndices, copies));
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-static void doInline (bool bDeep) {
-	vector<LDObject*> sel = g_win->sel ();
-	
-	// History stuff
-	vector<LDSubfile*> paRefs;
-	vector<ulong> ulaRefIndices, ulaBitIndices;
-	
-	for (LDObject* obj : sel) {
-		if (obj->getType() != LDObject::Subfile)
-			continue;
-		
-		ulaRefIndices.push_back (obj->getIndex (g_curfile));
-		paRefs.push_back (static_cast<LDSubfile*> (obj)->clone ());
-	}
-	
-	for (LDObject* obj : sel) {
-		// Get the index of the subfile so we know where to insert the
-		// inlined contents.
-		long idx = obj->getIndex (g_curfile);
-		if (idx == -1)
-			continue;
-		
-		vector<LDObject*> objs;
-		
-		if (obj->getType() == LDObject::Subfile)
-			objs = static_cast<LDSubfile*> (obj)->inlineContents (bDeep, true);
-		else if (obj->getType() == LDObject::Radial)
-			objs = static_cast<LDRadial*> (obj)->decompose (true);
-		else
-			continue;
-		
-		// Merge in the inlined objects
-		for (LDObject* inlineobj : objs) {
-			ulaBitIndices.push_back (idx);
-			
-			// This object is now inlined so it has no parent anymore.
-			inlineobj->parent = null;
-			
-			g_curfile->insertObj (idx++, inlineobj);
-		}
-		
-		// Delete the subfile now as it's been inlined.
-		g_curfile->forgetObject (obj);
-		delete obj;
-	}
-	
-	History::addEntry (new InlineHistory (ulaBitIndices, ulaRefIndices, paRefs, bDeep));
-	g_win->refresh ();
-}
-
-MAKE_ACTION (inlineContents, "Inline", "inline", "Inline selected subfiles.", CTRL (I)) {
-	doInline (false);
-}
-
-MAKE_ACTION (deepInline, "Deep Inline", "inline-deep", "Recursively inline selected subfiles "
-	"down to polygons only.", CTRL_SHIFT (I))
-{
-	doInline (true);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (splitQuads, "Split Quads", "quad-split", "Split quads into triangles.", (0)) {
-	vector<LDObject*> objs = g_win->sel ();
-	
-	vector<ulong> ulaIndices;
-	vector<LDQuad*> paCopies;
-	
-	// Store stuff first for history archival
-	for (LDObject* obj : objs) {
-		if (obj->getType() != LDObject::Quad)
-			continue;
-		
-		ulaIndices.push_back (obj->getIndex (g_curfile));
-		paCopies.push_back (static_cast<LDQuad*> (obj)->clone ());
-	}
-	
-	for (LDObject* obj : objs) {
-		if (obj->getType() != LDObject::Quad)
-			continue;
-		
-		// Find the index of this quad
-		long lIndex = obj->getIndex (g_curfile);
-		
-		if (lIndex == -1)
-			return;
-		
-		std::vector<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles ();
-		
-		// Replace the quad with the first triangle and add the second triangle
-		// after the first one.
-		g_curfile->m_objs[lIndex] = triangles[0];
-		g_curfile->insertObj (lIndex + 1, triangles[1]);
-		
-		// Delete this quad now, it has been split.
-		delete obj;
-	}
-	
-	History::addEntry (new QuadSplitHistory (ulaIndices, paCopies));
-	g_win->refresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (setContents, "Set Contents", "set-contents", "Set the raw code of this object.", KEY (F9)) {
-	if (g_win->sel ().size() != 1)
-		return;
-	
-	LDObject* obj = g_win->sel ()[0];
-	SetContentsDialog::staticDialog (obj);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (setColor, "Set Color", "palette", "Set the color on given objects.", KEY (F10)) {
-	if (g_win->sel ().size() <= 0)
-		return;
-	
-	short dColor;
-	short dDefault = -1;
-	
-	std::vector<LDObject*> objs = g_win->sel ();
-	
-	// If all selected objects have the same color, said color is our default
-	// value to the color selection dialog.
-	dDefault = g_win->getSelectedColor ();
-	
-	// Show the dialog to the user now and ask for a color.
-	if (ColorSelectDialog::staticDialog (dColor, dDefault, g_win)) {
-		std::vector<ulong> ulaIndices;
-		std::vector<short> daColors;
-		
-		for (LDObject* obj : objs) {
-			if (obj->dColor != -1) {
-				ulaIndices.push_back (obj->getIndex (g_curfile));
-				daColors.push_back (obj->dColor);
-				
-				obj->dColor = dColor;
-			}
-		}
-		
-		History::addEntry (new SetColorHistory (ulaIndices, daColors, dColor));
-		g_win->refresh ();
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (makeBorders, "Make Borders", "make-borders", "Add borders around given polygons.",
-	CTRL_SHIFT (B))
-{
-	vector<LDObject*> objs = g_win->sel ();
-	
-	vector<ulong> ulaIndices;
-	vector<LDObject*> paObjs;
-	
-	for (LDObject* obj : objs) {
-		if (obj->getType() != LDObject::Quad && obj->getType() != LDObject::Triangle)
-			continue;
-		
-		short dNumLines;
-		LDLine* lines[4];
-		
-		if (obj->getType() == LDObject::Quad) {
-			dNumLines = 4;
-			
-			LDQuad* quad = static_cast<LDQuad*> (obj);
-			lines[0] = new LDLine (quad->vaCoords[0], quad->vaCoords[1]);
-			lines[1] = new LDLine (quad->vaCoords[1], quad->vaCoords[2]);
-			lines[2] = new LDLine (quad->vaCoords[2], quad->vaCoords[3]);
-			lines[3] = new LDLine (quad->vaCoords[3], quad->vaCoords[0]);
-		} else {
-			dNumLines = 3;
-			
-			LDTriangle* tri = static_cast<LDTriangle*> (obj);
-			lines[0] = new LDLine (tri->vaCoords[0], tri->vaCoords[1]);
-			lines[1] = new LDLine (tri->vaCoords[1], tri->vaCoords[2]);
-			lines[2] = new LDLine (tri->vaCoords[2], tri->vaCoords[0]); 
-		}
-		
-		for (short i = 0; i < dNumLines; ++i) {
-			ulong idx = obj->getIndex (g_curfile) + i + 1;
-			
-			lines[i]->dColor = edgecolor;
-			g_curfile->insertObj (idx, lines[i]);
-			
-			ulaIndices.push_back (idx);
-			paObjs.push_back (lines[i]->clone ());
-		}
-	}
-	
-	History::addEntry (new AddHistory (ulaIndices, paObjs));
-	g_win->refresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (makeCornerVerts, "Make Corner Vertices", "corner-verts",
-	"Adds vertex objects to the corners of the given polygons", (0))
-{
-	vector<ulong> ulaIndices;
-	vector<LDObject*> paObjs;
-	
-	for (LDObject* obj : g_win->sel ()) {
-		if (obj->vertices () < 2)
-			continue;
-		
-		ulong idx = obj->getIndex (g_curfile);
-		for (short i = 0; i < obj->vertices(); ++i) {
-			LDVertex* vert = new LDVertex;
-			vert->vPosition = obj->vaCoords[i];
-			vert->dColor = obj->dColor;
-			
-			g_curfile->insertObj (++idx, vert);
-			ulaIndices.push_back (idx);
-			paObjs.push_back (vert->clone ());
-		}
-	}
-	
-	if (ulaIndices.size() > 0) {
-		History::addEntry (new AddHistory (ulaIndices, paObjs));
-		g_win->refresh ();
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-static void doMoveSelection (const bool bUp) {
-	vector<LDObject*> objs = g_win->sel ();
-	
-	// Get the indices of the objects for history archival
-	vector<ulong> ulaIndices;
-	for (LDObject* obj : objs)
-		ulaIndices.push_back (obj->getIndex (g_curfile));
-	
-	LDObject::moveObjects (objs, bUp);
-	History::addEntry (new ListMoveHistory (ulaIndices, bUp));
-	g_win->buildObjList ();
-}
-
-MAKE_ACTION (moveUp, "Move Up", "arrow-up", "Move the current selection up.", SHIFT (Up)) {
-	doMoveSelection (true);
-}
-
-MAKE_ACTION (moveDown, "Move Down", "arrow-down", "Move the current selection down.", SHIFT (Down)) {
-	doMoveSelection (false);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (undo, "Undo", "undo", "Undo a step.", CTRL (Z)) {
-	History::undo ();
-}
-
-MAKE_ACTION (redo, "Redo", "redo", "Redo a step.", CTRL_SHIFT (Z)) {
-	History::redo ();
-}
-
-MAKE_ACTION (showHistory, "Edit History", "history", "Show the history dialog.", (0)) {
-	HistoryDialog dlg;
-	dlg.exec ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void doMoveObjects (vertex vVector) {
-	vector<ulong> ulaIndices;
-	
-	// Apply the grid values
-	vVector[X] *= currentGrid ().confs[Grid::X]->value;
-	vVector[Y] *= currentGrid ().confs[Grid::Y]->value;
-	vVector[Z] *= currentGrid ().confs[Grid::Z]->value;
-	
-	for (LDObject* obj : g_win->sel ()) {
-		ulaIndices.push_back (obj->getIndex (g_curfile));
-		obj->move (vVector);
-	}
-	
-	History::addEntry (new MoveHistory (ulaIndices, vVector));
-	g_win->refresh ();
-}
-
-MAKE_ACTION (moveXNeg, "Move -X", "move-x-neg", "Move selected objects negative on the X axis.", KEY (Left)) {
-	doMoveObjects ({-1, 0, 0});
-}
-
-MAKE_ACTION (moveYNeg, "Move -Y", "move-y-neg", "Move selected objects negative on the Y axis.", KEY (PageUp)) {
-	doMoveObjects ({0, -1, 0});
-}
-
-MAKE_ACTION (moveZNeg, "Move -Z", "move-z-neg", "Move selected objects negative on the Z axis.", KEY (Down)) {
-	doMoveObjects ({0, 0, -1});
-}
-
-MAKE_ACTION (moveXPos, "Move +X", "move-x-pos", "Move selected objects positive on the X axis.", KEY (Right)) {
-	doMoveObjects ({1, 0, 0});
-}
-
-MAKE_ACTION (moveYPos, "Move +Y", "move-y-pos", "Move selected objects positive on the Y axis.", KEY (PageDown)) {
-	doMoveObjects ({0, 1, 0});
-}
-
-MAKE_ACTION (moveZPos, "Move +Z", "move-z-pos", "Move selected objects positive on the Z axis.", KEY (Up)) {
-	doMoveObjects ({0, 0, 1});
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// invert - reverse winding
-// 
-// NOTE: History management gets a little tricky here. For lines, cond-lines,
-// triangles and quads, we edit the object, thus we record an EditHistory. For
-// subfiles and radials we create or delete invertnext objects. Since we have
-// multiple actions of different types, we store a history entry for each of
-// them and wrap them into a ComboHistory, which allows storage of multiple
-// simultaneous edits with different type. This is what we ultimately store into
-// History.
-// =============================================================================
-MAKE_ACTION (invert, "Invert", "invert", "Reverse the winding of given objects.", CTRL_SHIFT (W)) {
-	std::vector<LDObject*> paSelection = g_win->sel ();
-	std::vector<HistoryEntry*> paHistory;
-	
-	for (LDObject* obj : paSelection) {
-		// For the objects we end up editing, we store information into these
-		// variables and we store them into an EditHistory after the switch
-		// block. Subfile and radial management is stored into the history
-		// list immediately.
-		ulong ulHistoryIndex = obj->getIndex (g_curfile);
-		LDObject* pOldCopy, *pNewCopy;
-		bool bEdited = false;
-		
-		switch (obj->getType ()) {
-		case LDObject::Line:
-		case LDObject::CondLine:
-			{
-				// For lines, we swap the vertices. I don't think that a
-				// cond-line's control points need to be swapped, do they?
-				LDLine* pLine = static_cast<LDLine*> (obj);
-				vertex vTemp = pLine->vaCoords[0];
-				
-				pOldCopy = pLine->clone ();
-				pLine->vaCoords[0] = pLine->vaCoords[1];
-				pLine->vaCoords[1] = vTemp;
-				pNewCopy = pLine->clone ();
-				bEdited = true;
-			}
-			break;
-		
-		case LDObject::Triangle:
-			{
-				// Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1.
-				// Thus, we swap 1 and 2.
-				LDTriangle* pTri = static_cast<LDTriangle*> (obj);
-				vertex vTemp = pTri->vaCoords[1];
-				
-				pOldCopy = pTri->clone ();
-				pTri->vaCoords[1] = pTri->vaCoords[2];
-				pTri->vaCoords[2] = vTemp;
-				pNewCopy = pTri->clone ();
-				bEdited = true;
-			}
-			break;
-		
-		case LDObject::Quad:
-			{
-				// Quad: 0 -> 1 -> 2 -> 3
-				// rev:  0 -> 3 -> 2 -> 1
-				// Thus, we swap 1 and 3.
-				LDQuad* pQuad = static_cast<LDQuad*> (obj);
-				vertex vTemp = pQuad->vaCoords[1];
-				
-				pOldCopy = pQuad->clone ();
-				pQuad->vaCoords[1] = pQuad->vaCoords[3];
-				pQuad->vaCoords[3] = vTemp;
-				pNewCopy = pQuad->clone ();
-				bEdited = true;
-			}
-			break;
-		
-		case LDObject::Subfile:
-		case LDObject::Radial:
-			{
-				// Subfiles and radials are inverted when they're prefixed with
-				// a BFC INVERTNEXT statement. Thus we need to toggle this status.
-				// For flat primitives it's sufficient that the determinant is
-				// flipped but I don't have a method for checking flatness yet.
-				// Food for thought...
-				
-				bool inverted = false;
-				ulong idx = obj->getIndex (g_curfile);
-				
-				if (idx > 0) {
-					LDObject* prev = g_curfile->object (idx - 1);
-					LDBFC* bfc = dynamic_cast<LDBFC*> (prev);
-					
-					if (bfc && bfc->type == LDBFC::InvertNext) {
-						// Object is prefixed with an invertnext, thus remove it.
-						paHistory.push_back (new DelHistory ({idx - 1}, {bfc->clone ()}));
-						
-						inverted = true;
-						g_curfile->forgetObject (bfc);
-						delete bfc;
-					}
-				}
-				
-				if (!inverted) {
-					// Not inverted, thus prefix it with a new invertnext.
-					LDBFC* bfc = new LDBFC (LDBFC::InvertNext);
-					g_curfile->insertObj (idx, bfc);
-					
-					paHistory.push_back (new AddHistory ({idx}, {bfc->clone ()}));
-				}
-			}
-			break;
-		
-		default:
-			break;
-		}
-		
-		// If we edited this object, store the EditHistory based on collected
-		// information now.
-		if (bEdited == true)
-			paHistory.push_back (new EditHistory ({ulHistoryIndex}, {pOldCopy}, {pNewCopy}));
-	}
-	
-	if (paHistory.size () > 0) {
-		History::addEntry (new ComboHistory (paHistory));
-		g_win->refresh ();
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-static void doRotate (const short l, const short m, const short n) {
-	std::vector<LDObject*> sel = g_win->sel ();
-	bbox box;
-	vertex origin;
-	std::vector<vertex*> queue;
-	const double angle = (pi * currentGrid ().confs[Grid::Angle]->value) / 360;
-	
-	// ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2
-	matrix<3> transform ({
-		(l * l * (1 - cos (angle))) + cos (angle),
-		(m * l * (1 - cos (angle))) - (n * sin (angle)),
-		(n * l * (1 - cos (angle))) + (m * sin (angle)),
-		
-		(l * m * (1 - cos (angle))) + (n * sin (angle)),
-		(m * m * (1 - cos (angle))) + cos (angle),
-		(n * m * (1 - cos (angle))) - (l * sin (angle)),
-		
-		(l * n * (1 - cos (angle))) - (m * sin (angle)),
-		(m * n * (1 - cos (angle))) + (l * sin (angle)),
-		(n * n * (1 - cos (angle))) + cos (angle)
-	});
-	
-	// Calculate center vertex
-	for (LDObject* obj : sel) {
-		if (obj->getType () == LDObject::Subfile)
-			box << static_cast<LDSubfile*> (obj)->vPosition;
-		else if (obj->getType () == LDObject::Radial)
-			box << static_cast<LDRadial*> (obj)->vPosition;
-		else
-			box << obj;
-	}
-	
-	origin = box.center ();
-	
-	// Apply the above matrix to everything
-	for (LDObject* obj : sel) {
-		if (obj->vertices ())
-			for (short i = 0; i < obj->vertices (); ++i)
-				queue.push_back (&obj->vaCoords[i]);
-		else if (obj->getType () == LDObject::Subfile) {
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			
-			queue.push_back (&ref->vPosition);
-			ref->mMatrix = ref->mMatrix * transform;
-		} else if (obj->getType () == LDObject::Radial) {
-			LDRadial* rad = static_cast<LDRadial*> (obj);
-			
-			queue.push_back (&rad->vPosition);
-			rad->mMatrix = rad->mMatrix * transform;
-		} else if (obj->getType () == LDObject::Vertex)
-			queue.push_back (&static_cast<LDVertex*> (obj)->vPosition);
-	}
-	
-	for (vertex* v : queue) {
-		v->move (-origin);
-		v->transform (transform, g_origin);
-		v->move (origin);
-	}
-	
-	g_win->refresh ();
-}
-
-MAKE_ACTION (rotateXPos, "Rotate +X", "rotate-x-pos", "Rotate objects around X axis", CTRL (Right)) {
-	doRotate (1, 0, 0);
-}
-
-MAKE_ACTION (rotateYPos, "Rotate +Y", "rotate-y-pos", "Rotate objects around Y axis", CTRL (PageDown)) {
-	doRotate (0, 1, 0);
-}
-
-MAKE_ACTION (rotateZPos, "Rotate +Z", "rotate-z-pos", "Rotate objects around Z axis", CTRL (Up)) {
-	doRotate (0, 0, 1);
-}
-
-MAKE_ACTION (rotateXNeg, "Rotate -X", "rotate-x-neg", "Rotate objects around X axis", CTRL (Left)) {
-	doRotate (-1, 0, 0);
-}
-
-MAKE_ACTION (rotateYNeg, "Rotate -Y", "rotate-y-neg", "Rotate objects around Y axis", CTRL (PageUp)) {
-	doRotate (0, -1, 0);
-}
-
-MAKE_ACTION (rotateZNeg, "Rotate -Z", "rotate-z-neg", "Rotate objects around Z axis", CTRL (Down)) {
-	doRotate (0, 0, -1);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (roundCoords, "Round Coordinates", "round-coords", "Round coordinates down to 3/4 decimals", (0)) {
-	setlocale (LC_ALL, "C");
-	
-	for (LDObject* obj : g_win->sel ())
-	for (short i = 0; i < obj->vertices (); ++i)
-	for (const Axis ax : g_Axes)
-		obj->vaCoords[i][ax] = atof (fmt ("%.3f", obj->vaCoords[i][ax]));
-	
-	g_win->refresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MAKE_ACTION (uncolorize, "Uncolorize", "uncolorize", "Reduce colors of everything selected to main and edge colors", (0)) {
-	vector<LDObject*> oldCopies, newCopies;
-	vector<ulong> indices;
-	
-	for (LDObject* obj : g_win->sel ()) {
-		if (obj->isColored () == false)
-			continue;
-		
-		indices.push_back (obj->getIndex (g_curfile));
-		oldCopies.push_back (obj->clone ());
-		
-		obj->dColor = (obj->getType () == LDObject::Line || obj->getType () == LDObject::CondLine) ? edgecolor : maincolor;
-		newCopies.push_back (obj->clone ());
-	}
-	
-	if (indices.size () > 0) {
-		History::addEntry (new EditHistory (indices, oldCopies, newCopies));
-		g_win->refresh ();
-	}
-}
\ No newline at end of file
--- a/history.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,351 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "history.h"
-#include "ldtypes.h"
-#include "file.h"
-#include "misc.h"
-#include "gui.h"
-
-EXTERN_ACTION (undo)
-EXTERN_ACTION (redo)
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-namespace History {
-	std::vector<HistoryEntry*> s_entries;
-	static long s_pos = -1;
-	
-	// =========================================================================
-	void addEntry (HistoryEntry* entry) {
-		// If there's any entries ahead the current position, we need to
-		// remove them now
-		for (ulong i = s_pos + 1; i < s_entries.size(); ++i) {
-			delete s_entries[i];
-			s_entries.erase (s_entries.begin() + i);
-		}
-		
-		s_entries.push_back (entry);
-		s_pos++;
-		
-		updateActions ();
-	}
-	
-	// =========================================================================
-	void undo () {
-		if (s_pos == -1)
-			return; // nothing to undo
-		
-		s_entries[s_pos--]->undo ();
-		updateActions ();
-	}
-	
-	// =========================================================================
-	void redo () {
-		if (s_pos == (long) s_entries.size () - 1)
-			return; // nothing to redo;
-		
-		s_entries[++s_pos]->redo ();
-		updateActions ();
-	}
-	
-	// =========================================================================
-	void clear () {
-		for (HistoryEntry* entry : s_entries)
-			delete entry;
-		
-		s_entries.clear ();
-		s_pos = -1;
-		updateActions ();
-	}
-	
-	// =========================================================================
-	void updateActions () {
-		ACTION (undo)->setEnabled (s_pos > -1);
-		ACTION (redo)->setEnabled (s_pos < (long) s_entries.size () - 1);
-		
-		// Update the window title as well
-		g_win->setTitle ();
-	}
-	
-	// =========================================================================
-	long pos () {
-		return s_pos;
-	}
-	
-	// =========================================================================
-	std::vector<HistoryEntry*>& entries () {
-		return s_entries;
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void DelHistory::undo () {
-	for (ulong i = 0; i < cache.size(); ++i) {
-		ulong idx = cache.size() - i - 1;
-		LDObject* obj = cache[idx]->clone ();
-		g_curfile->insertObj (indices[idx], obj);
-	}
-	
-	g_win->refresh ();
-}
-
-// =============================================================================
-void DelHistory::redo () {
-	for (ulong i = 0; i < cache.size(); ++i) {
-		LDObject* obj = g_curfile->m_objs[indices[i]];
-		
-		g_curfile->forgetObject (obj);
-		delete obj;
-	}
-	
-	g_win->refresh ();
-}
-
-// =============================================================================
-DelHistory::~DelHistory () {
-	for (LDObject* obj : cache)
-		delete obj;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void SetColorHistory::undo () {
-	// Restore colors
-	for (ulong i = 0; i < ulaIndices.size (); ++i)
-		g_curfile->m_objs[ulaIndices[i]]->dColor = daColors[i];
-	
-	g_win->refresh ();
-}
-
-void SetColorHistory::redo () {
-	// Re-set post color
-	for (ulong i = 0; i < ulaIndices.size (); ++i)
-		g_curfile->m_objs[ulaIndices[i]]->dColor = dNewColor;
-	
-	g_win->refresh ();
-}
-
-SetColorHistory::~SetColorHistory () {}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void EditHistory::undo () {
-	for (ulong idx : ulaIndices)
-		g_curfile->object (idx)->replace (paOldObjs[idx]->clone ());
-	
-	g_win->refresh ();
-}
-
-void EditHistory::redo () {
-	for (ulong idx : ulaIndices)
-		g_curfile->object (idx)->replace (paNewObjs[idx]->clone ());
-	
-	g_win->refresh ();
-}
-
-EditHistory::~EditHistory () {
-	for (ulong idx : ulaIndices) {
-		delete paOldObjs[idx];
-		delete paNewObjs[idx];
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-std::vector<LDObject*> ListMoveHistory::getObjects (short ofs) {
-	std::vector<LDObject*> objs;
-	
-	for (ulong idx : ulaIndices)
-		objs.push_back (g_curfile->m_objs[idx + ofs]);
-	
-	return objs;
-}
-
-void ListMoveHistory::undo () {
-	std::vector<LDObject*> objs = getObjects (bUp ? -1 : 1);
-	LDObject::moveObjects (objs, !bUp);
-	g_win->buildObjList ();
-}
-
-void ListMoveHistory::redo () {
-	std::vector<LDObject*> objs = getObjects (0);
-	LDObject::moveObjects (objs, bUp);
-	g_win->buildObjList ();
-}
-
-ListMoveHistory::~ListMoveHistory() {}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-AddHistory::~AddHistory () {
-	for (LDObject* pObj : paObjs)
-		delete pObj;
-}
-
-void AddHistory::undo () {
-	for (ulong i = 0; i < paObjs.size(); ++i) {
-		ulong idx = ulaIndices[ulaIndices.size() - i - 1];
-		LDObject* obj = g_curfile->m_objs[idx];
-		
-		g_curfile->forgetObject (obj);
-		delete obj;
-	}
-	
-	g_win->refresh ();
-}
-
-void AddHistory::redo () {
-	for (ulong i = 0; i < paObjs.size(); ++i) {
-		ulong idx = ulaIndices[i];
-		LDObject* obj = paObjs[i]->clone ();
-		
-		g_curfile->insertObj (idx, obj);
-	}
-	
-	g_win->refresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-QuadSplitHistory::~QuadSplitHistory () {
-	for (LDQuad* pQuad : paQuads)
-		delete pQuad;
-}
-
-void QuadSplitHistory::undo () {
-	for (ulong i = 0; i < paQuads.size(); ++i) {
-		// The quad was replaced by the first triangle and a second one was
-		// added after it. Thus, we remove the second one here and replace
-		// the first with a copy of the quad.
-		ulong idx = ulaIndices[i];
-		
-		LDTriangle* tri1 = static_cast<LDTriangle*> (g_curfile->m_objs[idx]),
-			*tri2 = static_cast<LDTriangle*> (g_curfile->m_objs[idx + 1]);
-		LDQuad* pCopy = paQuads[i]->clone ();
-		
-		tri1->replace (pCopy);
-		g_curfile->forgetObject (tri2);
-		delete tri2;
-	}
-	
-	g_win->refresh ();
-}
-
-void QuadSplitHistory::redo () {
-	for (long i = paQuads.size() - 1; i >= 0; --i) {
-		ulong idx = ulaIndices[i];
-		
-		LDQuad* pQuad = static_cast<LDQuad*> (g_curfile->m_objs[idx]);
-		std::vector<LDTriangle*> paTriangles = pQuad->splitToTriangles ();
-		
-		g_curfile->m_objs[idx] = paTriangles[0];
-		g_curfile->insertObj (idx + 1, paTriangles[1]);
-		delete pQuad;
-	}
-	
-	g_win->refresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void InlineHistory::undo () {
-	for (long i = ulaBitIndices.size() - 1; i >= 0; --i) {
-		LDObject* obj = g_curfile->m_objs [ulaBitIndices[i]];
-		g_curfile->forgetObject (obj);
-		delete obj;
-	}
-	
-	for (ulong i = 0; i < ulaRefIndices.size(); ++i) {
-		LDSubfile* obj = paRefs[i]->clone ();
-		g_curfile->insertObj (ulaRefIndices[i], obj);
-	}
-	
-	g_win->refresh ();
-}
-
-void InlineHistory::redo () {
-	for (long i = ulaRefIndices.size() - 1; i >= 0; --i) {
-		ulong idx = ulaRefIndices[i];
-		
-		assert (g_curfile->object (idx)->getType () == LDObject::Subfile);
-		LDSubfile* ref = static_cast<LDSubfile*> (g_curfile->object (idx));
-		vector<LDObject*> objs = ref->inlineContents (bDeep, false);
-		
-		for (LDObject* obj : objs)
-			g_curfile->insertObj (idx++, obj);
-		
-		g_curfile->forgetObject (ref);
-		delete ref;
-	}
-	
-	g_win->refresh ();
-}
-
-InlineHistory::~InlineHistory () {
-	for (LDSubfile* ref : paRefs)
-		delete ref;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-MoveHistory::~MoveHistory () {}
-
-void MoveHistory::undo () {
-	const vertex vInverse = -vVector;
-	
-	for (ulong i : ulaIndices)
-		g_curfile->object (i)->move (vInverse);
-	g_win->refresh ();
-}
-
-void MoveHistory::redo () {
-	for (ulong i : ulaIndices)
-		g_curfile->object (i)->move (vVector);
-	g_win->refresh ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-ComboHistory::~ComboHistory () {
-	for (HistoryEntry* pEntry : paEntries)
-		delete pEntry;
-}
-
-void ComboHistory::undo () {
-	for (long i = paEntries.size() - 1; i >= 0; --i) {
-		HistoryEntry* pEntry = paEntries[i];
-		pEntry->undo ();
-	}
-}
-
-void ComboHistory::redo () {
-	for (HistoryEntry* pEntry : paEntries)
-		pEntry->redo ();
-}
\ No newline at end of file
--- a/history.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,215 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef HISTORY_H
-#define HISTORY_H
-
-#include "common.h"
-#include "ldtypes.h"
-
-#define IMPLEMENT_HISTORY_TYPE(N) \
-	virtual ~N##History (); \
-	virtual void undo (); \
-	virtual void redo (); \
-	virtual HistoryType type () { return HISTORY_##N; }
-
-// =============================================================================
-enum HistoryType {
-	HISTORY_Del,
-	HISTORY_SetColor,
-	HISTORY_Edit,
-	HISTORY_ListMove,
-	HISTORY_Add,
-	HISTORY_QuadSplit,
-	HISTORY_Inline,
-	HISTORY_Move,
-	HISTORY_Combo,
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class HistoryEntry {
-public:
-	virtual void undo () {}
-	virtual void redo () {}
-	virtual ~HistoryEntry () {}
-	virtual HistoryType type () { return (HistoryType)(0); }
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class DelHistory : public HistoryEntry {
-public:
-	enum Type {
-		Cut,	// were deleted with a cut operation
-		Other,	// were deleted witout specific reason
-	};
-	
-	IMPLEMENT_HISTORY_TYPE (Del)
-	
-	vector<ulong> indices;
-	vector<LDObject*> cache;
-	const Type eType;
-	
-	DelHistory (vector<ulong> indices, vector<LDObject*> cache, const Type eType = Other) :
-		indices (indices), cache (cache), eType (eType) {}
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class SetColorHistory : public HistoryEntry {
-public:
-	IMPLEMENT_HISTORY_TYPE (SetColor)
-	
-	vector<ulong> ulaIndices;
-	vector<short> daColors;
-	short dNewColor;
-	
-	SetColorHistory (vector<ulong> ulaIndices, vector<short> daColors, short dNewColor) :
-		ulaIndices (ulaIndices), daColors (daColors), dNewColor (dNewColor) {}
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class EditHistory : public HistoryEntry {
-public:
-	IMPLEMENT_HISTORY_TYPE (Edit)
-	
-	const std::vector<ulong> ulaIndices;
-	const std::vector<LDObject*> paOldObjs, paNewObjs;
-	
-	EditHistory (std::vector<ulong> ulaIndices, std::vector<LDObject*> paOldObjs,
-		std::vector<LDObject*> paNewObjs) :
-		ulaIndices (ulaIndices), paOldObjs (paOldObjs), paNewObjs (paNewObjs) {}
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class ListMoveHistory : public HistoryEntry {
-public:
-	IMPLEMENT_HISTORY_TYPE (ListMove)
-	
-	std::vector<ulong> ulaIndices;
-	bool bUp;
-	
-	std::vector<LDObject*> getObjects (short ofs);
-	ListMoveHistory (vector<ulong> ulaIndices, const bool bUp) :
-		ulaIndices (ulaIndices), bUp (bUp) {}
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class AddHistory : public HistoryEntry {
-public:
-	enum Type {
-		Other,	// was "just added"
-		Paste,	// was added through a paste operation
-	};
-	
-	IMPLEMENT_HISTORY_TYPE (Add)
-	
-	std::vector<ulong> ulaIndices;
-	std::vector<LDObject*> paObjs;
-	const Type eType;
-	
-	AddHistory (std::vector<ulong> ulaIndices, std::vector<LDObject*> paObjs,
-		const Type eType = Other) :
-		ulaIndices (ulaIndices), paObjs (paObjs), eType (eType) {}
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class QuadSplitHistory : public HistoryEntry {
-public:
-	IMPLEMENT_HISTORY_TYPE (QuadSplit)
-	
-	std::vector<ulong> ulaIndices;
-	std::vector<LDQuad*> paQuads;
-	
-	QuadSplitHistory (std::vector<ulong> ulaIndices, std::vector<LDQuad*> paQuads) :
-		ulaIndices (ulaIndices), paQuads (paQuads) {}
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class InlineHistory : public HistoryEntry {
-public:
-	IMPLEMENT_HISTORY_TYPE (Inline)
-	
-	const std::vector<ulong> ulaBitIndices, ulaRefIndices;
-	const std::vector<LDSubfile*> paRefs;
-	const bool bDeep;
-	
-	InlineHistory (const std::vector<ulong> ulaBitIndices, const std::vector<ulong> ulaRefIndices,
-		const std::vector<LDSubfile*> paRefs, const bool bDeep) :
-		ulaBitIndices (ulaBitIndices), ulaRefIndices (ulaRefIndices), paRefs (paRefs), bDeep (bDeep) {}
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class MoveHistory : public HistoryEntry {
-public:
-	IMPLEMENT_HISTORY_TYPE (Move)
-	
-	const std::vector<ulong> ulaIndices;
-	const vertex vVector;
-	
-	MoveHistory (const std::vector<ulong> ulaIndices, const vertex vVector) :
-		ulaIndices (ulaIndices), vVector (vVector) {}
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class ComboHistory : public HistoryEntry {
-public:
-	IMPLEMENT_HISTORY_TYPE (Combo)
-	
-	std::vector<HistoryEntry*> paEntries;
-	
-	ComboHistory (std::vector<HistoryEntry*> paEntries) : paEntries (paEntries) {}
-	
-	ComboHistory& operator<< (HistoryEntry* entry) {
-		paEntries.push_back (entry);
-		return *this;
-	}
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-namespace History {
-	void addEntry (HistoryEntry* entry);
-	void undo ();
-	void redo ();
-	void clear ();
-	void updateActions ();
-	long pos ();
-	std::vector<HistoryEntry*>& entries ();
-};
-
-#endif // HISTORY_H
\ No newline at end of file
--- a/ldforge.pro	Wed May 08 14:57:48 2013 +0300
+++ b/ldforge.pro	Wed May 08 15:19:06 2013 +0300
@@ -11,56 +11,8 @@
 RESOURCES = ldforge.qrc
 MOC_DIR = ./autogen/
 RCC_DIR = ./autogen/
-
-# Input
-HEADERS += bbox.h \
-	colors.h \
-	common.h \
-	config.h \
-	extprogs.h \
-	file.h \
-	gldraw.h \
-	gui.h \
-	history.h \
-	ldtypes.h \
-	misc.h \
-	radiobox.h \
-	str.h \
-	types.h \
-	zz_aboutDialog.h \
-	zz_addObjectDialog.h \
-	zz_colorSelectDialog.h \
-	zz_configDialog.h \
-	zz_ldrawPathDialog.h \
-	zz_historyDialog.h \
-	zz_newPartDialog.h \
-	zz_setContentsDialog.h
-
-SOURCES += \
-	config.cpp \
-	gui.cpp \
-	gldraw.cpp \
-	bbox.cpp \
-	colors.cpp \
-	extprogs.cpp \
-	file.cpp \
-	gui_actions.cpp \
-	gui_editactions.cpp \
-	history.cpp \
-	ldtypes.cpp \
-	main.cpp \
-	misc.cpp \
-	radiobox.cpp \
-	str.cpp \ 
-	types.cpp \
-	zz_aboutDialog.cpp \
-	zz_addObjectDialog.cpp \
-	zz_colorSelectDialog.cpp \
-	zz_configDialog.cpp \
-	zz_ldrawPathDialog.cpp \
-	zz_historyDialog.cpp \
-	zz_newPartDialog.cpp \
-	zz_setContentsDialog.cpp
+SOURCES = ./src/*.cpp
+HEADERS = ./src/*.h
 
 QMAKE_CXXFLAGS += -std=c++0x
 QT += opengl
--- a/ldtypes.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,675 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "common.h"
-#include "ldtypes.h"
-#include "file.h"
-#include "misc.h"
-#include "gui.h"
-
-char const* g_saObjTypeNames[] = {
-	"subfile",
-	"radial",
-	"quadrilateral",
-	"triangle",
-	"line",
-	"condline",
-	"vertex",
-	"bfc",
-	"comment",
-	"unknown",
-	"empty",
-	"unidentified",
-};
-
-// Should probably get rid of this array sometime
-char const* g_saObjTypeIcons[] = {
-	"subfile",
-	"radial",
-	"quad",
-	"triangle",
-	"line",
-	"condline",
-	"vertex",
-	"bfc",
-	"comment",
-	"error",
-	"empty",
-	"error",
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-// LDObject constructors
-LDObject::LDObject () {
-	qObjListEntry = null;
-	parent = null;
-	m_hidden = false;
-}
-
-LDGibberish::LDGibberish (str _zContent, str _zReason) {
-	zContents = _zContent;
-	zReason = _zReason;
-}
-
-// =============================================================================
-str LDComment::getContents () {
-	return fmt ("0 %s", text.chars ());
-}
-
-str LDSubfile::getContents () {
-	str val = fmt ("1 %d %s ", dColor, vPosition.stringRep (false).chars ());
-	val += mMatrix.stringRep ();
-	val += ' ';
-	val += zFileName;
-	return val;
-}
-
-str LDLine::getContents () {
-	str val = fmt ("2 %d", dColor);
-	
-	for (ushort i = 0; i < 2; ++i)
-		val.appendformat (" %s", vaCoords[i].stringRep (false).chars ());
-	
-	return val;
-}
-
-str LDTriangle::getContents () {
-	str val = fmt ("3 %d", dColor);
-	
-	for (ushort i = 0; i < 3; ++i)
-		val.appendformat (" %s", vaCoords[i].stringRep (false).chars ());
-	
-	return val;
-}
-
-str LDQuad::getContents () {
-	str val = fmt ("4 %d", dColor);
-	
-	for (ushort i = 0; i < 4; ++i)
-		val.appendformat (" %s", vaCoords[i].stringRep (false).chars ());
-	
-	return val;
-}
-
-str LDCondLine::getContents () {
-	str val = fmt ("5 %d", dColor);
-	
-	// Add the coordinates
-	for (ushort i = 0; i < 4; ++i)
-		val.appendformat (" %s", vaCoords[i].stringRep (false).chars ());
-	
-	return val;
-}
-
-str LDGibberish::getContents () {
-	return zContents;
-}
-
-str LDVertex::getContents () {
-	return fmt ("0 !LDFORGE VERTEX %d %s", dColor, vPosition.stringRep (false).chars());
-}
-
-str LDEmpty::getContents () {
-	return str ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-const char* LDBFC::statements[] = {
-	"CERTIFY CCW",
-	"CCW",
-	"CERTIFY CW",
-	"CW",
-	"NOCERTIFY",
-	"INVERTNEXT",
-};
-
-str LDBFC::getContents () {
-	return fmt ("0 BFC %s", LDBFC::statements[type]);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-vector<LDTriangle*> LDQuad::splitToTriangles () {
-	// Create the two triangles based on this quadrilateral:
-	// 0---3     0---3    3
-	// |   |     |  /    /|
-	// |   |  =  | /    / |
-	// |   |     |/    /  |
-	// 1---2     1    1---2
-	LDTriangle* tri1 = new LDTriangle;
-	tri1->vaCoords[0] = vaCoords[0];
-	tri1->vaCoords[1] = vaCoords[1];
-	tri1->vaCoords[2] = vaCoords[3];
-	
-	LDTriangle* tri2 = new LDTriangle;
-	tri2->vaCoords[0] = vaCoords[1];
-	tri2->vaCoords[1] = vaCoords[2];
-	tri2->vaCoords[2] = vaCoords[3];
-	
-	// The triangles also inherit the quad's color
-	tri1->dColor = tri2->dColor = dColor;
-	
-	vector<LDTriangle*> triangles;
-	triangles.push_back (tri1);
-	triangles.push_back (tri2);
-	return triangles;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void LDObject::replace (LDObject* replacement) {
-	// Replace all instances of the old object with the new object
-	for (LDObject*& obj : g_curfile->m_objs)
-		if (obj == this)
-			obj = replacement;
-	
-	// Remove the old object
-	delete this;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void LDObject::swap (LDObject* other) {
-	for (LDObject*& obj : g_curfile->m_objs) {
-		if (obj == this)
-			obj = other;
-		else if (obj == other)
-			obj = this;
-	}
-}
-
-LDLine::LDLine (vertex v1, vertex v2) {
-	vaCoords[0] = v1;
-	vaCoords[1] = v2;
-}
-
-LDObject::~LDObject () {
-	// Remove this object from the selection array if it is there.
-	for (ulong i = 0; i < g_win->sel ().size(); ++i)
-		if (g_win->sel ()[i] == this)
-			g_win->sel ().erase (g_win->sel ().begin() + i);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-static void transformObject (LDObject* obj, matrix<3> transform, vertex pos, short parentcolor) {
-	switch (obj->getType()) {
-	case LDObject::Line:
-	case LDObject::CondLine:
-	case LDObject::Triangle:
-	case LDObject::Quad:
-		for (short i = 0; i < obj->vertices (); ++i)
-			obj->vaCoords[i].transform (transform, pos);
-		break;
-	
-	case LDObject::Subfile:
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			
-			matrix<3> newMatrix = transform * ref->mMatrix;
-			ref->vPosition.transform (transform, pos);
-			ref->mMatrix = newMatrix;
-		}
-		break;
-	
-	default:
-		break;
-	}
-	
-	if (obj->dColor == maincolor)
-		obj->dColor = parentcolor;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-vector<LDObject*> LDSubfile::inlineContents (bool bDeepInline, bool bCache) {
-	vector<LDObject*> objs, cache;
-	
-	// If we have this cached, just clone that
-	if (bDeepInline && pFile->m_objCache.size ()) {
-		for (LDObject* obj : pFile->m_objCache)
-			objs.push_back (obj->clone ());
-	} else {
-		if (!bDeepInline)
-			bCache = false;
-		
-		for (LDObject* obj : pFile->m_objs) {
-			// Skip those without schemantic meaning
-			switch (obj->getType ()) {
-			case LDObject::Comment:
-			case LDObject::Empty:
-			case LDObject::Gibberish:
-			case LDObject::Unidentified:
-			case LDObject::Vertex:
-				continue;
-			
-			case LDObject::BFC:
-				// Filter non-INVERTNEXT statements
-				if (static_cast<LDBFC*> (obj)->type != LDBFC::InvertNext)
-					continue;
-				break;
-			
-			default:
-				break;
-			}
-			
-			// Got another sub-file reference, inline it if we're deep-inlining. If not,
-			// just add it into the objects normally. Also, we only cache immediate
-			// subfiles and this is not one. Yay, recursion!
-			if (bDeepInline && obj->getType() == LDObject::Subfile) {
-				LDSubfile* ref = static_cast<LDSubfile*> (obj);
-				
-				vector<LDObject*> otherobjs = ref->inlineContents (true, false);
-				
-				for (LDObject* otherobj : otherobjs) {
-					// Cache this object, if desired
-					if (bCache)
-						cache.push_back (otherobj->clone ());
-					
-					objs.push_back (otherobj);
-				}
-			} else {
-				if (bCache)
-					cache.push_back (obj->clone ());
-				
-				objs.push_back (obj->clone ());
-			}
-		}
-		
-		if (bCache)
-			pFile->m_objCache = cache;
-	}
-	
-	// Transform the objects
-	for (LDObject* obj : objs) {
-		// Set the parent now so we know what inlined this.
-		obj->parent = this;
-		
-		transformObject (obj, mMatrix, vPosition, dColor);
-	}
-	
-	return objs;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-long LDObject::getIndex (OpenFile* pFile) {
-	for (ulong i = 0; i < pFile->m_objs.size(); ++i)
-		if (pFile->m_objs[i] == this)
-			return i;
-	
-	return -1;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void LDObject::moveObjects (std::vector<LDObject*> objs, const bool bUp) {
-	// If we move down, we need to iterate the array in reverse order.
-	const long start = bUp ? 0 : (objs.size() - 1);
-	const long end = bUp ? objs.size() : -1;
-	const long incr = bUp ? 1 : -1;
-	
-	for (long i = start; i != end; i += incr) {
-		LDObject* obj = objs[i];
-		
-		const long lIndex = obj->getIndex (g_curfile),
-			lTarget = lIndex + (bUp ? -1 : 1);
-		
-		if ((bUp == true and lIndex == 0) or
-			(bUp == false and lIndex == (long)(g_curfile->m_objs.size() - 1)))
-		{
-			// One of the objects hit the extrema. If this happens, this should be the first
-			// object to be iterated on. Thus, nothing has changed yet and it's safe to just
-			// abort the entire operation.
-			assert (i == start);
-			return;
-		}
-		
-		obj->swap (g_curfile->m_objs[lTarget]);
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-str LDObject::objectListContents (const std::vector<LDObject*>& objs) {
-	bool firstDetails = true;
-	str text = "";
-	
-	if (objs.size() == 0)
-		return "nothing"; // :)
-	
-	for (long i = 0; i < LDObject::NumTypes; ++i) {
-		LDObject::Type objType = (LDObject::Type) i;
-		ulong objCount = 0;
-		
-		for (LDObject* obj : objs)
-			if (obj->getType() == objType)
-				objCount++;
-		
-		if (objCount == 0)
-			continue;
-		
-		if (!firstDetails)
-			text += ", ";
-		
-		str noun = fmt ("%s%s", g_saObjTypeNames[objType], PLURAL (objCount));
-		
-		// Plural of "vertex" is "vertices". Stupid English.
-		if (objType == LDObject::Vertex && objCount != 1)
-			noun = "vertices";
-		
-		text.appendformat ("%lu %s", objCount, noun.chars ());
-		firstDetails = false;
-	}
-	
-	return text;
-}
-
-// =============================================================================
-LDObject* LDObject::topLevelParent () {
-	if (!parent)
-		return null;
-	
-	LDObject* it = this;
-	
-	while (it->parent)
-		it = it->parent;
-	
-	return it;
-}
-
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void LDObject::move (vertex vVector) { vVector = vVector; /* to shut up GCC */ }
-void LDEmpty::move (vertex vVector) { vVector = vVector; }
-void LDBFC::move (vertex vVector) { vVector = vVector; }
-void LDComment::move (vertex vVector) { vVector = vVector; }
-void LDGibberish::move (vertex vVector) { vVector = vVector; }
-
-void LDVertex::move (vertex vVector) {
-	vPosition += vVector;
-}
-
-void LDSubfile::move (vertex vVector) {
-	vPosition += vVector;
-}
-
-void LDRadial::move (vertex vVector) {
-	vPosition += vVector;
-}
-
-void LDLine::move (vertex vVector) {
-	for (short i = 0; i < 2; ++i)
-		vaCoords[i] += vVector;
-}
-
-void LDTriangle::move (vertex vVector) {
-	for (short i = 0; i < 3; ++i)
-		vaCoords[i] += vVector;
-}
-
-void LDQuad::move (vertex vVector) {
-	for (short i = 0; i < 4; ++i)
-		vaCoords[i] += vVector;
-}
-
-void LDCondLine::move (vertex vVector) {
-	for (short i = 0; i < 4; ++i)
-		vaCoords[i] += vVector;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-static char const* g_saRadialTypeNames[] = {
-	"Circle",
-	"Cylinder",
-	"Disc",
-	"Disc Negative",
-	"Ring",
-	"Cone",
-	null
-};
-
-char const* LDRadial::radialTypeName () {
-	return g_saRadialTypeNames[eRadialType];
-}
-
-char const* LDRadial::radialTypeName (const LDRadial::Type eType) {
-	return g_saRadialTypeNames[eType];
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-std::vector<LDObject*> LDRadial::decompose (bool bTransform) {
-	std::vector<LDObject*> paObjects;
-	
-	for (short i = 0; i < dSegments; ++i) {
-		double x0 = cos ((i * 2 * pi) / dDivisions),
-			x1 = cos (((i + 1) * 2 * pi) / dDivisions),
-			z0 = sin ((i * 2 * pi) / dDivisions),
-			z1 = sin (((i + 1) * 2 * pi) / dDivisions);
-		
-		switch (eRadialType) {
-		case LDRadial::Circle:
-			{
-				vertex v0 (x0, 0.0f, z0),
-					v1 (x1, 0.0f, z1);
-				
-				if (bTransform) {
-					v0.transform (mMatrix, vPosition);
-					v1.transform (mMatrix, vPosition);
-				}
-				
-				LDLine* pLine = new LDLine;
-				pLine->vaCoords[0] = v0;
-				pLine->vaCoords[1] = v1;
-				pLine->dColor = edgecolor;
-				pLine->parent = this;
-				
-				paObjects.push_back (pLine);
-			}
-			break;
-		
-		case LDRadial::Cylinder:
-		case LDRadial::Ring:
-		case LDRadial::Cone:
-			{
-				double x2, x3, z2, z3;
-				double y0, y1, y2, y3;
-				
-				if (eRadialType == LDRadial::Cylinder) {
-					x2 = x1;
-					x3 = x0;
-					z2 = z1;
-					z3 = z0;
-					
-					y0 = y1 = 0.0f;
-					y2 = y3 = 1.0f;
-				} else {
-					x2 = x1 * (dRingNum + 1);
-					x3 = x0 * (dRingNum + 1);
-					z2 = z1 * (dRingNum + 1);
-					z3 = z0 * (dRingNum + 1);
-					
-					x0 *= dRingNum;
-					x1 *= dRingNum;
-					z0 *= dRingNum;
-					z1 *= dRingNum;
-					
-					if (eRadialType == LDRadial::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);
-				
-				if (bTransform) {
-					v0.transform (mMatrix, vPosition);
-					v1.transform (mMatrix, vPosition);
-					v2.transform (mMatrix, vPosition);
-					v3.transform (mMatrix, vPosition);
-				}
-				
-				LDQuad* pQuad = new LDQuad;
-				pQuad->vaCoords[0] = v0;
-				pQuad->vaCoords[1] = v1;
-				pQuad->vaCoords[2] = v2;
-				pQuad->vaCoords[3] = v3;
-				pQuad->dColor = dColor;
-				pQuad->parent = this;
-				
-				paObjects.push_back (pQuad);
-			}
-			break;
-		
-		case LDRadial::Disc:
-		case LDRadial::DiscNeg:
-			{
-				double x2, z2;
-				
-				if (eRadialType == LDRadial::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);
-				
-				if (bTransform) {
-					v0.transform (mMatrix, vPosition);
-					v1.transform (mMatrix, vPosition);
-					v2.transform (mMatrix, vPosition);
-				}
-				
-				LDTriangle* pSeg = new LDTriangle;
-				pSeg->vaCoords[0] = v0;
-				pSeg->vaCoords[1] = v1;
-				pSeg->vaCoords[2] = v2;
-				pSeg->dColor = dColor;
-				pSeg->parent = this;
-				
-				paObjects.push_back (pSeg);
-			}
-			break;
-		
-		default:
-			break;
-		}
-	}
-	
-	return paObjects;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-str LDRadial::getContents () {
-	return fmt ("0 !LDFORGE RADIAL %s %d %d %d %d %s %s",
-		str (radialTypeName()).toupper ().strip (' ').chars (),
-		dColor, dSegments, dDivisions, dRingNum,
-		vPosition.stringRep (false).chars(), mMatrix.stringRep().chars());
-}
-
-char const* g_saRadialNameRoots[] = {
-	"edge",
-	"cyli",
-	"disc",
-	"ndis",
-	"ring",
-	"cone",
-	null
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-str LDRadial::makeFileName () {
-	short numer = dSegments,
-		denom = dDivisions;
-	
-	// Simplify the fractional part, but the denominator must be at least 4.
-	simplify (numer, denom);
-	
-	if (denom < 4) {
-		const short factor = (4 / denom);
-		
-		numer *= factor;
-		denom *= factor;
-	}
-	
-	// Compose some general information: prefix, fraction, root, ring number
-	str prefix = (dDivisions == 16) ? "" : fmt ("%d/", dDivisions);
-	str frac = fmt ("%d-%d", numer, denom);
-	str root = g_saRadialNameRoots[eRadialType];
-	str ringNum = (eRadialType == Ring || eRadialType == Cone) ? fmt ("%d", dRingNum) : "";
-	
-	// Truncate the root if necessary (7-16rin4.dat for instance).
-	// However, always keep the root at least 2 characters.
-	short extra = (~frac + ~ringNum + ~root) - 8;
-	root -= min<short> (max<short> (extra, 0), 2);
-	
-	// Stick them all together and return the result.
-	return fmt ("%s%s%s%s", prefix.chars(), frac.chars (), root.chars (), ringNum.chars ());
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-#define CHECK_FOR_OBJ(N) \
-	if (type == LDObject::N) \
-		return new LD##N;
-LDObject* LDObject::getDefault (const LDObject::Type type) {
-	CHECK_FOR_OBJ (Comment)
-	CHECK_FOR_OBJ (BFC)
-	CHECK_FOR_OBJ (Line)
-	CHECK_FOR_OBJ (CondLine)
-	CHECK_FOR_OBJ (Radial)
-	CHECK_FOR_OBJ (Subfile)
-	CHECK_FOR_OBJ (Triangle)
-	CHECK_FOR_OBJ (Quad)
-	CHECK_FOR_OBJ (Empty)
-	CHECK_FOR_OBJ (BFC)
-	CHECK_FOR_OBJ (Gibberish)
-	CHECK_FOR_OBJ (Vertex)
-	return null;
-}
\ No newline at end of file
--- a/ldtypes.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,387 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef LDTYPES_H
-#define LDTYPES_H
-
-#include "common.h"
-#include "types.h"
-
-#define IMPLEMENT_LDTYPE(T, NUMVERTS) \
-	LD##T () {} \
-	virtual ~LD##T () {} \
-	virtual LDObject::Type getType () const { \
-		return LDObject::T; \
-	} \
-	virtual str getContents (); \
-	virtual LD##T* clone () { \
-		return new LD##T (*this); \
-	} \
-	virtual void move (vertex vVector); \
-	virtual short vertices () const { return NUMVERTS; } \
-
-#define LDOBJ_SETCOLORED(V) virtual bool isColored () const { return V; }
-#define LDOBJ_COLORED LDOBJ_SETCOLORED (true)
-#define LDOBJ_UNCOLORED LDOBJ_SETCOLORED (false)
-
-#define LDOBJ_CUSTOM_SCHEMANTIC virtual bool isSchemantic () const
-#define LDOBJ_SCHEMANTIC LDOBJ_CUSTOM_SCHEMANTIC { return true; }
-#define LDOBJ_NON_SCHEMANTIC LDOBJ_CUSTOM_SCHEMANTIC { return false; }
-
-class QListWidgetItem;
-class LDSubfile;
-
-// =============================================================================
-// LDObject
-// 
-// Base class object for all LD* types. Each LDObject represents a single line
-// in the LDraw code file. The virtual method getType returns an enumerator
-// which is a token of the object's type. The object can be casted into
-// sub-classes based on this enumerator.
-// =============================================================================
-class LDObject {
-public:
-	// Object type codes. Codes are sorted in order of significance.
-	enum Type {
-		Subfile,		// Object represents a sub-file reference
-		Radial,			// Object represents a generic radial
-		Quad,			// Object represents a quadrilateral
-		Triangle,		// Object represents a triangle
-		Line,			// Object represents a line
-		CondLine,		// Object represents a conditional line
-		Vertex,			// Object is a vertex, LDForge extension object
-		BFC,			// Object represents a BFC statement
-		Comment,		// Object represents a comment
-		Gibberish,		// Object is the result of failed parsing
-		Empty,			// Object represents an empty line
-		Unidentified,	// Object is an uninitialized (SHOULD NEVER HAPPEN)
-		NumTypes		// Amount of object types
-	};
-	
-	LDObject ();
-	virtual ~LDObject ();
-	
-	// Index (i.e. line number) of this object
-	long getIndex (OpenFile* pFile);
-	
-	// Color used by this object. Comments, gibberish and empty entries
-	// do not use this field.
-	short dColor;
-	
-	// OpenGL list for this object
-	uint uGLList, uGLPickList;
-	
-	// Vertices of this object
-	vertex vaCoords[4];
-	
-	// Object this object was referenced from, if any
-	LDObject* parent;
-	
-	// Type enumerator of this object
-	virtual LDObject::Type getType () const {
-		return LDObject::Unidentified;
-	};
-	
-	// A string that represents this line
-	virtual str getContents () {
-		return "";
-	}
-	
-	// Creates a new LDObject identical to this one and returns a pointer to it.
-	virtual LDObject* clone () {
-		return new LDObject (*this);
-	}
-	
-	// Replace this LDObject with another LDObject. This method deletes the
-	// object and any pointers to it become invalid.
-    void replace (LDObject* replacement);
-	
-	// Swap this object with another.
-	void swap (LDObject* other);
-	
-	// Moves this object using the given vertex as a movement vector
-	virtual void move (vertex vVector);
-	
-	// What object in the current file ultimately references this?
-	LDObject* topLevelParent ();
-	
-	// Number of vertices this object has
-	virtual short vertices () const { return 0; }
-	
-	// Is this object colored?
-	virtual bool isColored () const { return false; }
-	
-	// Does this object have meaning in the part model?
-	virtual bool isSchemantic () const { return false; }
-	
-	// Returns a sample object by the given value
-	static LDObject* getDefault (const LDObject::Type type);
-	
-	static void moveObjects (std::vector<LDObject*> objs, const bool bUp);
-	static str objectListContents (const std::vector<LDObject*>& objs);
-	
-	// Object list entry for this object
-	QListWidgetItem* qObjListEntry;
-	
-	bool hidden () const { return m_hidden; }
-	void setHidden (const bool hidden) { m_hidden = hidden; }
-
-private:
-	bool m_hidden;
-};
-
-// =============================================================================
-// LDGibberish
-//
-// Represents a line in the LDraw file that could not be properly parsed. It is
-// represented by a (!) ERROR in the code view. It exists for the purpose of
-// allowing garbage lines be debugged and corrected within LDForge. The member
-// zContent contains the contents of the unparsable line.
-// =============================================================================
-class LDGibberish : public LDObject {
-public:
-	IMPLEMENT_LDTYPE (Gibberish, 0)
-	LDOBJ_UNCOLORED
-	LDOBJ_SCHEMANTIC
-	
-	LDGibberish (str _zContent, str _zReason);
-	
-	// Content of this unknown line
-	str zContents;
-	
-	// Why is this gibberish?
-	str zReason;
-};
-
-// =============================================================================
-// LDEmptyLine 
-// 
-// Represents an empty line in the LDraw code file.
-// =============================================================================
-class LDEmpty : public LDObject {
-public:
-	IMPLEMENT_LDTYPE (Empty, 0)
-	LDOBJ_UNCOLORED
-	LDOBJ_NON_SCHEMANTIC
-};
-
-// =============================================================================
-// LDComment
-//
-// Represents a code-0 comment in the LDraw code file. Member zText contains
-// the text of the comment.
-// =============================================================================
-class LDComment : public LDObject {
-public:
-	IMPLEMENT_LDTYPE (Comment, 0)
-	LDOBJ_UNCOLORED
-	LDOBJ_NON_SCHEMANTIC
-	
-	LDComment (str zText) : text (zText) {}
-	
-	str text; // The text of this comment
-};
-
-// =============================================================================
-// LDBFC
-// 
-// Represents a 0 BFC statement in the LDraw code. eStatement contains the type
-// of this statement.
-// =============================================================================
-class LDBFC : public LDComment {
-public:
-	enum Type {
-		CertifyCCW,
-		CCW,
-		CertifyCW,
-		CW,
-		NoCertify,	// Winding becomes disabled (0 BFC NOCERTIFY)
-		InvertNext,	// Winding is inverted for next object (0 BFC INVERTNEXT)
-		NumStatements
-	};
-	
-	IMPLEMENT_LDTYPE (BFC, 0)
-	LDOBJ_UNCOLORED
-	LDOBJ_CUSTOM_SCHEMANTIC { return (type == InvertNext); }
-	
-	LDBFC (const LDBFC::Type eType) : type (eType) {}
-	
-	// Statement strings
-	static const char* statements[];
-	
-	Type type;
-};
-
-// =============================================================================
-// LDSubfile
-//
-// Represents a single code-1 subfile reference.
-// =============================================================================
-class LDSubfile : public LDObject {
-public:
-	IMPLEMENT_LDTYPE (Subfile, 0)
-	LDOBJ_COLORED
-	LDOBJ_SCHEMANTIC
-	
-	vertex vPosition; // Position of the subpart (FIXME: should get rid of this)
-	matrix<3> mMatrix; // Transformation matrix for the subpart
-	str zFileName; // Filename of the subpart
-	OpenFile* pFile; // Pointer to opened file for this subfile. null if unopened.
-	
-	// Inlines this subfile. Note that return type is an array of heap-allocated
-	// LDObject-clones, they must be deleted one way or another.
-	std::vector<LDObject*> inlineContents (bool bDeepInline, bool bCache);
-};
-
-// =============================================================================
-// LDLine
-//
-// Represents a single code-2 line in the LDraw code file. v0 and v1 are the end
-// points of the line. The line is colored with dColor unless uncolored mode is
-// set.
-// =============================================================================
-class LDLine : public LDObject {
-public:
-	IMPLEMENT_LDTYPE (Line, 2)
-	LDOBJ_COLORED
-	LDOBJ_SCHEMANTIC
-	
-	LDLine (vertex v1, vertex v2);
-};
-
-// =============================================================================
-// LDCondLine
-//
-// Represents a single code-5 conditional line. The end-points v0 and v1 are
-// inherited from LDLine, c0 and c1 are the control points of this line.
-// =============================================================================
-class LDCondLine : public LDLine {
-public:
-	IMPLEMENT_LDTYPE (CondLine, 4)
-	LDOBJ_COLORED
-	LDOBJ_SCHEMANTIC
-};
-
-// =============================================================================
-// LDTriangle
-//
-// Represents a single code-3 triangle in the LDraw code file. Vertices v0, v1
-// and v2 contain the end-points of this triangle. dColor is the color the
-// triangle is colored with.
-// =============================================================================
-class LDTriangle : public LDObject {
-public:
-	IMPLEMENT_LDTYPE (Triangle, 3)
-	LDOBJ_COLORED
-	LDOBJ_SCHEMANTIC
-	
-	LDTriangle (vertex _v0, vertex _v1, vertex _v2) {
-		vaCoords[0] = _v0;
-		vaCoords[1] = _v1;
-		vaCoords[2] = _v2;
-	}
-};
-
-// =============================================================================
-// LDQuad
-//
-// Represents a single code-4 quadrilateral. v0, v1, v2 and v3 are the end points
-// of the quad, dColor is the color used for the quad.
-// =============================================================================
-class LDQuad : public LDObject {
-public:
-	IMPLEMENT_LDTYPE (Quad, 4)
-	LDOBJ_COLORED
-	LDOBJ_SCHEMANTIC
-	
-	// Split this quad into two triangles (note: heap-allocated)
-	vector<LDTriangle*> splitToTriangles ();
-};
-
-// =============================================================================
-// LDVertex
-// 
-// The vertex is an LDForce-specific extension which represents a single
-// vertex which can be used as a parameter to tools or to store coordinates
-// with. Vertices are a part authoring tool and they should not appear in
-// finished parts.
-// =============================================================================
-class LDVertex : public LDObject {
-public:
-	IMPLEMENT_LDTYPE (Vertex, 0) // TODO: move vPosition to vaCoords[0]
-	LDOBJ_COLORED
-	LDOBJ_NON_SCHEMANTIC
-	
-	vertex vPosition;
-};
-
-// =============================================================================
-// LDRadial
-// 
-// The generic radial primitive (radial for short) is another LDforge-specific
-// extension which represents an arbitrary circular primitive. Radials can appear
-// as circles, cylinders, rings, cones, discs and disc negatives; the point is to
-// allow part authors to add radial primitives to parts without much hassle about
-// non-existant primitive parts.
-// =============================================================================
-class LDRadial : public LDObject {
-public:
-	enum Type {
-		Circle,
-		Cylinder,
-		Disc,
-		DiscNeg,
-		Ring,
-		Cone,
-		NumTypes
-	};
-	
-	IMPLEMENT_LDTYPE (Radial, 0)
-	LDOBJ_COLORED
-	LDOBJ_SCHEMANTIC
-	
-	LDRadial::Type eRadialType;
-	vertex vPosition;
-	matrix<3> mMatrix;
-	short dDivisions, dSegments, dRingNum;
-	
-	LDRadial (LDRadial::Type eRadialType, vertex vPosition, matrix<3> mMatrix,
-		short dDivisions, short dSegments, short dRingNum) :
-		eRadialType (eRadialType), vPosition (vPosition), mMatrix (mMatrix),
-		dDivisions (dDivisions), dSegments (dSegments), dRingNum (dRingNum) {}
-	
-	// Returns a set of objects that provide the equivalent of this radial.
-	// Note: objects are heap-allocated.
-	std::vector<LDObject*> decompose (bool bTransform);
-	
-	// Compose a file name for this radial.
-	str makeFileName ();
-	
-	char const* radialTypeName ();
-	static char const* radialTypeName (const LDRadial::Type eType);
-};
-
-// =============================================================================
-// Object type names. Pass the return value of getType as the index to get a
-// string representation of the object's type.
-extern const char* g_saObjTypeNames[];
-
-// Icons for these types
-extern const char* g_saObjTypeIcons[];
-
-#endif // LDTYPES_H
\ No newline at end of file
--- a/main.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QApplication>
-#include "gui.h"
-#include "file.h"
-#include "bbox.h"
-#include "misc.h"
-#include "config.h"
-#include "colors.h"
-#include "types.h"
-
-vector<OpenFile*> g_loadedFiles;
-OpenFile* g_curfile = null;
-ForgeWindow* g_win = null; 
-bbox g_BBox;
-const QApplication* g_app = null;
-
-const vertex g_origin (0.0f, 0.0f, 0.0f);
-const matrix<3> g_identity ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-int main (int argc, char* argv[]) {
-	// Load or create the configuration
-	if (!config::load()) {
-		printf ("Creating configuration file...\n");
-		if (config::save ())
-			printf ("Configuration file successfully created.\n");
-		else
-			printf ("failed to create configuration file!\n");
-	}
-	
-	const QApplication app (argc, argv);
-	LDPaths::initPaths ();
-	
-	initColors ();
-	initPartList ();
-	
-	ForgeWindow* win = new ForgeWindow;
-	
-	g_app = &app;
-	
-	newFile ();
-	
-	win->show ();
-	return app.exec ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void logf (const char* fmtstr, ...) {
-	va_list va;
-	va_start (va, fmtstr);
-	g_win->logVA (LOG_Normal, fmtstr, va);
-	va_end (va);
-}
-
-void logf (LogType type, const char* fmtstr, ...) {
-	va_list va;
-	va_start (va, fmtstr);
-	g_win->logVA (type, fmtstr, va);
-	va_end (va);
-}
-
-void warnf (const char* fmtstr, ...) {
-	va_list va;
-	va_start (va, fmtstr);
-	g_win->logVA (LOG_Warning, fmtstr, va);
-	va_end (va);
-}
-
-void errf (const char* fmtstr, ...) {
-	va_list va;
-	va_start (va, fmtstr);
-	g_win->logVA (LOG_Error, fmtstr, va);
-	va_end (va);
-}
-
-#ifndef RELEASE
-void devf (const char* fmtstr, ...) {
-	va_list va;
-	va_start (va, fmtstr);
-	g_win->logVA (LOG_Dev, fmtstr, va);
-	va_end (va);
-}
-#endif // RELEASE
\ No newline at end of file
--- a/misc.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,277 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <math.h>
-#include <locale.h>
-#include <qcolor.h>
-#include "common.h"
-#include "misc.h"
-#include "gui.h"
-
-// Prime number table.
-const ushort g_uaPrimes[NUM_PRIMES] = {
-	   2,    3,    5,    7,   11,   13,   17,   19,   23,   29, 
-	  31,   37,   41,   43,   47,   53,   59,   61,   67,   71, 
-	  73,   79,   83,   89,   97,  101,  103,  107,  109,  113, 
-	 127,  131,  137,  139,  149,  151,  157,  163,  167,  173, 
-	 179,  181,  191,  193,  197,  199,  211,  223,  227,  229, 
-	 233,  239,  241,  251,  257,  263,  269,  271,  277,  281, 
-	 283,  293,  307,  311,  313,  317,  331,  337,  347,  349, 
-	 353,  359,  367,  373,  379,  383,  389,  397,  401,  409, 
-	 419,  421,  431,  433,  439,  443,  449,  457,  461,  463, 
-	 467,  479,  487,  491,  499,  503,  509,  521,  523,  541, 
-	 547,  557,  563,  569,  571,  577,  587,  593,  599,  601, 
-	 607,  613,  617,  619,  631,  641,  643,  647,  653,  659, 
-	 661,  673,  677,  683,  691,  701,  709,  719,  727,  733, 
-	 739,  743,  751,  757,  761,  769,  773,  787,  797,  809, 
-	 811,  821,  823,  827,  829,  839,  853,  857,  859,  863, 
-	 877,  881,  883,  887,  907,  911,  919,  929,  937,  941, 
-	 947,  953,  967,  971,  977,  983,  991,  997, 1009, 1013, 
-	1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 
-	1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 
-	1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 
-	1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 
-	1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 
-	1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 
-	1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 
-	1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 
-	1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 
-	1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 
-	1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 
-	1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 
-	1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 
-	1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 
-	2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 
-	2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 
-	2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 
-	2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 
-	2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 
-	2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 
-	2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 
-	2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 
-	2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 
-	2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 
-	2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 
-	2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 
-	3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, 
-	3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 
-	3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 
-	3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 
-	3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 
-	3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 
-	3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-// Grid stuff
-cfg (int, grid, Grid::Medium);
-EXTERN_ACTION (gridCoarse)
-EXTERN_ACTION (gridMedium)
-EXTERN_ACTION (gridFine)
-
-cfg (float, grid_coarse_x,			5.0f);
-cfg (float, grid_coarse_y,			5.0f);
-cfg (float, grid_coarse_z,			5.0f);
-cfg (float, grid_coarse_angle,	45.0f);
-cfg (float, grid_medium_x,			1.0f);
-cfg (float, grid_medium_y,			1.0f);
-cfg (float, grid_medium_z,			1.0f);
-cfg (float, grid_medium_angle,	22.5f);
-cfg (float, grid_fine_x,			0.1f);
-cfg (float, grid_fine_y,			0.1f);
-cfg (float, grid_fine_z,			0.1f);
-cfg (float, grid_fine_angle,		7.5f);
-
-const gridinfo g_GridInfo[3] = {
-	{ "Coarse",	{ &grid_coarse_x,	&grid_coarse_y,	&grid_coarse_z,	&grid_coarse_angle	}, &ACTION (gridCoarse) },
-	{ "Medium",	{ &grid_medium_x,	&grid_medium_y,		&grid_medium_z,		&grid_medium_angle	}, &ACTION (gridMedium) },
-	{ "Fine",		{ &grid_fine_x,	&grid_fine_y,		&grid_fine_z,		&grid_fine_angle	}, &ACTION (gridFine) }
-};
-
-// =============================================================================
-double Grid::snap (double in, const Grid::Config axis) {
-	const double gridval = currentGrid ().confs[axis]->value;
-	const long mult = abs (in / gridval);
-	const bool neg = (in < 0);
-	
-	double out = mult * gridval;
-	
-	if (abs<double> (in) - (mult * gridval) > gridval / 2)
-		out += gridval;
-	
-	if (neg && out != 0)
-		out *= -1;
-	
-	return out;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-str ftoa (double fCoord) {
-	// Disable the locale first so that the decimal point will not
-	// turn into anything weird (like commas)
-	setlocale (LC_NUMERIC, "C");
-	
-	str zRep = fmt ("%f", fCoord);
-	
-	// Remove trailing zeroes
-	while (zRep[~zRep - 1] == '0')
-		zRep -= 1;
-	
-	// If there was only zeroes in the decimal place, remove
-	// the decimal point now.
-	if (zRep[~zRep - 1] == '.')
-		zRep -= 1;
-	
-	return zRep;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-bool isNumber (str& zToken) {
-	char* cpPointer = &zToken[0];
-	bool bGotDot = false;
-	
-	// Allow leading hyphen for negatives
-	if (*cpPointer == '-')
-		cpPointer++;
-	
-	while (*cpPointer != '\0') {
-		if (*cpPointer == '.' && !bGotDot) {
-			// Decimal point
-			bGotDot = true;
-			cpPointer++;
-			continue;
-		}
-		
-		if (*cpPointer >= '0' && *cpPointer <= '9') {
-			cpPointer++;
-			continue; // Digit
-		}
-		
-		// If the above cases didn't catch this character, it was
-		// illegal and this is therefore not a number.
-		return false;
-	}
-	
-	return true;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void simplify (short& dNum, short& dDenom) {
-	bool bRepeat;
-	
-	do {
-		bRepeat = false;
-		
-		for (ulong x = 0; x < NUM_PRIMES; x++) {
-			ulong i = NUM_PRIMES - x - 1;
-			ushort uPrime = g_uaPrimes[i];
-			
-			if (dNum <= uPrime || dDenom <= uPrime)
-				continue;
-			
-			if ((dNum % uPrime == 0) && (dDenom % uPrime == 0)) {
-				dNum /= uPrime;
-				dDenom /= uPrime;
-				bRepeat = true;
-				break;
-			}
-		}
-	} while (bRepeat);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-StringParser::StringParser (str zInText, char cSeparator) {
-	zaTokens = zInText.split (cSeparator, true);
-	dPos = -1;
-}
-
-// -----------------------------------------------------------------------------
-bool StringParser::atBeginning () {
-	return (dPos == -1);
-}
-
-// -----------------------------------------------------------------------------
-bool StringParser::atEnd () {
-	return (dPos == (signed) zaTokens.size () - 1);
-}
-
-// -----------------------------------------------------------------------------
-bool StringParser::getToken (str& zVal, const ushort uInPos) {
-	if (uInPos >= zaTokens.size())
-		return false;
-	
-	zVal = zaTokens[uInPos];
-	return true;
-}
-
-// -----------------------------------------------------------------------------
-bool StringParser::next (str& zVal) {
-	return getToken (zVal, ++dPos);
-}
-
-// -----------------------------------------------------------------------------
-bool StringParser::peekNext (str& zVal) {
-	return getToken (zVal, dPos + 1);
-}
-
-// -----------------------------------------------------------------------------
-bool StringParser::findToken (short& dResult, char const* sNeedle, short dArgs) {
-	for (ushort i = 0; i < (zaTokens.size () - dArgs); ++i) {
-		if (zaTokens[i] == sNeedle) {
-			dResult = i;
-			return true;
-		}
-	}
-	
-	return false;
-}
-
-// -----------------------------------------------------------------------------
-void StringParser::rewind () {
-	dPos = -1;
-}
-
-// -----------------------------------------------------------------------------
-void StringParser::seek (short int dAmount, bool bRelative) {
-	dPos = (bRelative ? dPos : 0) + dAmount;
-}
-
-// -----------------------------------------------------------------------------
-size_t StringParser::size () {
-	return zaTokens.size();
-}
-
-// -----------------------------------------------------------------------------
-bool StringParser
-::tokenCompare (short int dInPos, const char* sOther) {
-	str tok;
-	if (!getToken (tok, dInPos))
-		return false;
-	
-	return (tok == sOther);
-}
\ No newline at end of file
--- a/misc.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef MISC_H
-#define MISC_H
-
-#include "common.h"
-#include "str.h"
-#include "config.h"
-
-#define NUM_PRIMES 500
-
-class QColor;
-class QAction;
-
-// Prime numbers
-extern const ushort g_uaPrimes[NUM_PRIMES];
-
-// Returns whether a given string represents a floating point number.
-bool isNumber (str& zToken);
-
-// Converts a float value to a string value.
-str ftoa (double fCoord);
-
-// Simplifies the given fraction.
-void simplify (short& dNum, short& dDenom);
-
-// Grid stuff
-typedef struct {
-	const char* const name;
-	floatconfig* const confs[4];
-	QAction** const actionptr;
-} gridinfo;
-
-extern_cfg (int, grid);
-static const short g_NumGrids = 3;
-extern const gridinfo g_GridInfo[3];
-
-inline const gridinfo& currentGrid () {
-	return g_GridInfo[grid];
-}
-
-namespace Grid {
-	enum Type {
-		Coarse,
-		Medium,
-		Fine
-	};
-	
-	enum Config {
-		X,
-		Y,
-		Z,
-		Angle
-	};
-	
-	double snap (double value, const Grid::Config axis);
-};
-
-// =============================================================================
-template<class T> void dataswap (T& a, T& b) {
-	T c = a;
-	a = b;
-	b = c;
-}
-
-// =============================================================================
-// StringParser
-//
-// String parsing utility
-// =============================================================================
-class StringParser {
-public:
-	std::vector<str> zaTokens;
-	short dPos;
-	
-	StringParser (str zInText, char cSeparator);
-	
-	bool atEnd ();
-	bool atBeginning ();
-	bool next (str& zVal);
-	bool peekNext (str& zVal);
-	bool getToken (str& zVal, const ushort uInPos);
-	bool findToken (short& dResult, char const* sNeedle, short dArgs);
-	size_t size ();
-	void rewind ();
-	void seek (short dAmount, bool bRelative);
-	bool tokenCompare (short int dInPos, const char* sOther);
-	
-	str operator[] (const size_t uIndex) {
-		return zaTokens[uIndex];
-	}
-};
-
-#endif // MISC_H
\ No newline at end of file
--- a/radiobox.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <qboxlayout.h>
-#include <qradiobutton.h>
-#include "radiobox.h"
-
-static QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) {
-	return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom;
-}
-
-void RadioBox::init (Qt::Orientation orient) {
-	dir = makeDirection (orient);
-	
-	buttonGroup = new QButtonGroup;
-	currentId = 0;
-	coreLayout = null;
-	
-	coreLayout = new QBoxLayout (makeDirection (orient, true));
-	setLayout (coreLayout);
-	
-	// Init the first row with a break
-	rowBreak ();
-	
-	connect (buttonGroup, SIGNAL (buttonPressed (QAbstractButton*)), this, SLOT (slot_buttonPressed (QAbstractButton*)));
-	connect (buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int)));
-}
-
-RadioBox::RadioBox (const QString& title, initlist<char const*> entries, int const defaultId,
-	const Qt::Orientation orient, QWidget* parent) : QGroupBox (title, parent), defaultId (defaultId)
-{
-	init (orient);
-	
-	for (char const* entry : entries)
-		addButton (entry);
-}
-
-void RadioBox::rowBreak () {
-	QBoxLayout* newLayout = new QBoxLayout (dir);
-	currentLayout = newLayout;
-	layouts.push_back (newLayout);
-	
-	coreLayout->addLayout (newLayout);
-}
-
-void RadioBox::addButton (const char* entry) {
-	QRadioButton* button = new QRadioButton (entry);
-	addButton (button);
-}
-
-void RadioBox::addButton (QRadioButton* button) {
-	bool const selectThis = (currentId == defaultId);
-	
-	objects.push_back (button);
-	buttonGroup->addButton (button, currentId++);
-	currentLayout->addWidget (button);
-	
-	if (selectThis)
-		button->setChecked (true);
-}
-
-RadioBox& RadioBox::operator<< (QRadioButton* button) {
-	addButton (button);
-	return *this;
-}
-
-RadioBox& RadioBox::operator<< (const char* entry) {
-	addButton (entry);
-	return *this;
-}
-
-void RadioBox::setCurrentRow (uint row) {
-	currentLayout = layouts[row];
-}
-
-void RadioBox::slot_buttonPressed (int btn) {
-	emit sig_buttonPressed (btn);
-}
-
-void RadioBox::slot_buttonPressed (QAbstractButton* btn) {
-	emit sig_buttonPressed (btn);
-}
\ No newline at end of file
--- a/radiobox.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef RADIOBOX_H
-#define RADIOBOX_H
-
-#include "common.h"
-#include <qwidget.h>
-#include <QGroupBox>
-#include <qradiobutton.h>
-#include <qboxlayout.h>
-#include <qbuttongroup.h>
-
-// =============================================================================
-// RadioBox
-//
-// Convenience widget - is a groupbox of radio buttons.
-// =============================================================================
-class RadioBox : public QGroupBox {
-	Q_OBJECT
-	
-public:
-	void init (Qt::Orientation orient);
-	
-	explicit RadioBox (QWidget* parent = null) : QGroupBox (parent) {
-		init (Qt::Vertical);
-	}
-	
-	explicit RadioBox (const QString& title, QWidget* parent = null) : QGroupBox (title, parent) {
-		init (Qt::Vertical);
-	}
-	
-	explicit RadioBox () {
-		init (Qt::Vertical);
-	}
-	
-	explicit RadioBox (const QString& title, initlist<char const*> entries, int const defaultId,
-		const Qt::Orientation orient = Qt::Vertical, QWidget* parent = null);
-	
-	void rowBreak ();
-	void setCurrentRow (uint row);
-	void addButton (const char* entry);
-	void addButton (QRadioButton* button);
-	RadioBox& operator<< (QRadioButton* button);
-	RadioBox& operator<< (const char* entry);
-	
-	int value () const {
-		return buttonGroup->checkedId ();
-	}
-	
-	void setValue (int val) {
-		buttonGroup->button (val)->setChecked (true);
-	}
-	
-	std::vector<QRadioButton*>::iterator begin () {
-		return objects.begin ();
-	}
-	
-	std::vector<QRadioButton*>::iterator end () {
-		return objects.end ();
-	}
-	
-	QRadioButton* operator[] (uint n) const {
-		return objects[n];
-	}
-	
-	bool exclusive () const {
-		return buttonGroup->exclusive ();
-	}
-	
-	void setExclusive (bool val) {
-		buttonGroup->setExclusive (val);
-	}
-	
-	bool isChecked (int n) const {
-		return buttonGroup->checkedId () == n;
-	}
-
-signals:
-	void sig_buttonPressed (int btn);
-	void sig_buttonPressed (QAbstractButton* btn);
-
-private:
-	std::vector<QRadioButton*> objects;
-	std::vector<QBoxLayout*> layouts;
-	QBoxLayout* coreLayout;
-	QBoxLayout* currentLayout;
-	QBoxLayout::Direction dir;
-	int currentId;
-	int defaultId;
-	QButtonGroup* buttonGroup;
-	
-	Q_DISABLE_COPY (RadioBox)
-
-private slots:
-	void slot_buttonPressed (int btn);
-	void slot_buttonPressed (QAbstractButton* btn);
-};
-
-#endif // RADIOBOX_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/aboutDialog.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,156 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <qlabel.h>
+#include <qboxlayout.h>
+#include <qdialogbuttonbox.h>
+#include <qdesktopservices.h>
+#include <qurl.h>
+#include "common.h"
+#include "aboutDialog.h"
+
+AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) {
+	QWidget* mainTab, *licenseTab;
+	QTabWidget* tabs = new QTabWidget;
+	
+	{
+		mainTab = new QWidget;
+		
+		// Application icon - in full 64 x 64 glory.
+		QLabel* icon = new QLabel;
+		icon->setPixmap (getIcon ("ldforge"));
+		
+		// Heading - application label and copyright information
+		QLabel* title = new QLabel (fmt ("<b>" APPNAME " v%d.%d</b><br />"
+			"Copyright (C) 2013 Santeri Piippo",
+			VERSION_MAJOR, VERSION_MINOR));
+		
+		// Body text
+		QLabel* info = new QLabel (
+			"<p>This software is intended for usage as a parts<br />"
+			"authoring tool for the <a href=\"http://ldraw.org/\">LDraw</a> parts library.</p>"
+			
+			"<p>" APPNAME " is free software, and you are welcome<br />"
+			"to redistribute it under the terms of GPL v3. See the LICENSE<br />"
+			"text file or the license tab in this dialog for details. If the<br />"
+			"license text is not available for some reason, see<br />"
+			"<a href=\"http://www.gnu.org/licenses/\">http://www.gnu.org/licenses/</a>"
+			"for the license terms.</p>"
+			
+			"<p>The application icon is derived from "
+			"<a href=\"http://en.wikipedia.org/wiki/File:Anvil,_labelled_en.svg\">this image</a>.</p>"
+		);
+		
+		// Rest in peace, James.
+		QLabel* memorial = new QLabel ("In living memory of James Jessiman.");
+		
+		QVBoxLayout* layout = new QVBoxLayout;
+		layout->addWidget (icon);
+		layout->addWidget (title);
+		layout->addWidget (info);
+		layout->addWidget (memorial);
+		
+		// Align everything to the center.
+		for (QLabel* label : vector<QLabel*> ({icon, title, info, memorial}))
+			label->setAlignment (Qt::AlignCenter);
+		
+		mainTab->setLayout (layout);
+		tabs->addTab (mainTab, "About " APPNAME);
+	}
+	
+	{
+		licenseTab = new QWidget;
+		
+		QTextEdit* license = new QTextEdit;
+		license->setReadOnly (true);
+		
+		QFont font ("Monospace");
+		font.setStyleHint (QFont::TypeWriter);
+		font.setPixelSize (10);
+		
+		license->setFont (font);
+		
+		// Make the text view wide enough to display the license text.
+		// Why isn't 80 sufficient here?
+		license->setMinimumWidth (license->fontMetrics ().width ('a') * 85);
+		
+		// Try open the license text
+		FILE* fp = fopen ("LICENSE", "r");
+		
+		if (fp == null) {
+			// Failed; tell the user how to get the license text instead.
+			setlocale (LC_ALL, "C");
+			char const* text = "Couldn't open LICENSE: %s.<br />"
+				"See <a href=\"http://www.gnu.org/licenses/\">http://www.gnu.org/licenses/</a> for the GPLv3 text.";
+			
+			license->setHtml (fmt (text, strerror (errno)));
+		} else {
+			// Figure out file size
+			fseek (fp, 0, SEEK_END);
+			const size_t length = ftell (fp);
+			rewind (fp);
+			
+			// Init text buffer and write pointer
+			char* licenseText = new char[length];
+			char* writePtr = &licenseText[0];
+			
+			// Read in the license text
+			while (true) {
+				*writePtr = fgetc (fp);
+				
+				if (feof (fp))
+					break;
+				
+				writePtr++;
+			}
+			
+			// Add terminating null character and add the license text to the
+			// license dialog text view.
+			*writePtr = '\0';
+			license->setText (licenseText);
+			
+			// And dump the trash on the way out.
+			delete[] licenseText;
+		}
+		
+		QVBoxLayout* layout = new QVBoxLayout;
+		layout->addWidget (license);
+		licenseTab->setLayout (layout);
+		tabs->addTab (licenseTab, "License");
+	}
+	
+	QDialogButtonBox* buttons = new QDialogButtonBox (QDialogButtonBox::Close);
+	QPushButton* helpButton = new QPushButton;
+	
+	helpButton->setText ("Mail Author");
+	helpButton->setIcon (getIcon ("mail"));
+	buttons->addButton (static_cast<QAbstractButton*> (helpButton), QDialogButtonBox::HelpRole);
+	connect (buttons, SIGNAL (helpRequested ()), this, SLOT (slot_mail ()));
+	connect (buttons, SIGNAL (rejected ()), this, SLOT (reject ()));
+	
+	QVBoxLayout* layout = new QVBoxLayout;
+	layout->addWidget (tabs);
+	layout->addWidget (buttons);
+	setLayout (layout);
+	setWindowTitle ("About " APPNAME);
+}
+
+void AboutDialog::slot_mail () {
+	QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <arezey@gmail.com>?subject=LDForge"));
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/aboutDialog.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,36 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ZZ_ABOUTDIALOG_H
+#define ZZ_ABOUTDIALOG_H
+
+#include <qdialog.h>
+#include "gui.h"
+
+class AboutDialog : public QDialog {
+	Q_OBJECT
+	
+public:
+	AboutDialog (QWidget* parent = nullptr, Qt::WindowFlags f = 0);
+	QPushButton* pb_mailAuthor;
+	
+private slots:
+	void slot_mail ();
+};
+
+#endif // ZZ_ABOUTDIALOG_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/addObjectDialog.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,511 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <qgridlayout.h>
+#include <qradiobutton.h>
+#include <qcheckbox.h>
+#include "gui.h"
+#include "addObjectDialog.h"
+#include "file.h"
+#include "colors.h"
+#include "colorSelectDialog.h"
+#include "history.h"
+#include "setContentsDialog.h"
+
+#define APPLY_COORDS(OBJ, N) \
+	for (short i = 0; i < N; ++i) \
+		for (const Axis ax : g_Axes) \
+			OBJ->vaCoords[i][ax] = dlg.dsb_coords[(i * 3) + ax]->value ();
+
+// =============================================================================
+class SubfileListItem : public QTreeWidgetItem {
+public:
+	SubfileListItem (QTreeWidgetItem* parent, int subfileID) :
+		QTreeWidgetItem (parent), subfileID (subfileID) {}
+	SubfileListItem (QTreeWidget* parent, int subfileID) :
+		QTreeWidgetItem (parent), subfileID (subfileID) {}
+	
+	int subfileID;
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) :
+	QDialog (parent)
+{
+	setlocale (LC_ALL, "C");
+	
+	short coordCount = 0;
+	
+	switch (type) {
+	case LDObject::Comment:
+		le_comment = new QLineEdit;
+		if (obj)
+			le_comment->setText (static_cast<LDComment*> (obj)->text);
+		break;
+	
+	case LDObject::Line:
+		coordCount = 6;
+		break;
+	
+	case LDObject::Triangle:
+		coordCount = 9;
+		break;
+	
+	case LDObject::Quad:
+	case LDObject::CondLine:
+		coordCount = 12;
+		break;
+	
+	case LDObject::Vertex:
+		coordCount = 3;
+		break;
+	
+	case LDObject::BFC:
+		rb_bfcType = new RadioBox ("Statement", {}, 0, Qt::Vertical);
+		
+		for (int i = 0; i < LDBFC::NumStatements; ++i)
+			rb_bfcType->addButton (new QRadioButton (LDBFC::statements[i]));
+		
+		if (obj)
+			rb_bfcType->setValue ((int) static_cast<LDBFC*> (obj)->type);
+		break;
+	
+	case LDObject::Subfile:
+		coordCount = 3;
+		
+		enum {
+			Parts,
+			Subparts,
+			Primitives,
+			HiRes,
+		};
+		
+		tw_subfileList = new QTreeWidget ();
+		for (int i : vector<int> ({Parts, Subparts, Primitives, HiRes})) {
+			SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, -1);
+			parentItem->setText (0, (i == Parts) ? "Parts" :
+				(i == Subparts) ? "Subparts" :
+				(i == Primitives) ? "Primitives" :
+				"Hi-Res");
+			
+			ulong j = 0;
+			for (partListEntry& part : g_PartList) {
+				QList<QTreeWidgetItem*> subfileItems;
+				
+				str fileName = part.sName;
+				const bool isSubpart = fileName.substr (0, 2) == "s\\";
+				const bool isPrimitive = str (part.sTitle).substr (0, 9) == "Primitive";
+				const bool isHiRes = fileName.substr (0, 3) == "48\\";
+				
+				if ((i == Subparts && isSubpart) ||
+					(i == Primitives && isPrimitive) ||
+					(i == HiRes && isHiRes) ||
+					(i == Parts && !isSubpart && !isPrimitive && !isHiRes))
+				{
+					SubfileListItem* item = new SubfileListItem (parentItem, j);
+					item->setText (0, fmt ("%s - %s", part.sName, part.sTitle));
+					subfileItems.append (item);
+				}
+				
+				j++;
+			}
+			
+			tw_subfileList->addTopLevelItem (parentItem);
+		}
+		
+		connect (tw_subfileList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_subfileTypeChanged ()));
+		lb_subfileName = new QLabel ("File:");
+		le_subfileName = new QLineEdit;
+		le_subfileName->setFocus ();
+		
+		if (obj) {
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			le_subfileName->setText (ref->zFileName);
+		}
+		break;
+	
+	case LDObject::Radial:
+		coordCount = 3;
+		
+		lb_radType = new QLabel ("Type:");
+		lb_radResolution = new QLabel ("Resolution:");
+		lb_radSegments = new QLabel ("Segments:");
+		lb_radRingNum = new QLabel ("Ring number:");
+		
+		rb_radType = new RadioBox ("Type", {}, 0, Qt::Vertical);
+		
+		for (int i = 0; i < LDRadial::NumTypes; ++i) {
+			if (i % (LDRadial::NumTypes / 2) == 0)
+				rb_radType->rowBreak ();
+			
+			rb_radType->addButton (new QRadioButton (LDRadial::radialTypeName ((LDRadial::Type) i)));
+		}
+		
+		connect (rb_radType, SIGNAL (sig_buttonPressed (int)), this, SLOT (slot_radialTypeChanged (int)));
+		
+		cb_radHiRes = new QCheckBox ("Hi-Res");
+		
+		sb_radSegments = new QSpinBox;
+		sb_radSegments->setMinimum (1);
+		
+		sb_radRingNum = new QSpinBox;
+		sb_radRingNum->setEnabled (false);
+		
+		if (obj) {
+			LDRadial* rad = static_cast<LDRadial*> (obj);
+			
+			rb_radType->setValue (rad->eRadialType);
+			sb_radSegments->setValue (rad->dSegments);
+			cb_radHiRes->setChecked ((rad->dDivisions == 48) ? Qt::Checked : Qt::Unchecked);
+			sb_radRingNum->setValue (rad->dRingNum);
+		}
+		break;
+	
+	default:
+		assert (false);
+		return;
+	}
+	
+	QPixmap icon = getIcon (fmt ("add-%s", g_saObjTypeIcons[type]));
+	LDObject* defaults = LDObject::getDefault (type);
+	
+	lb_typeIcon = new QLabel;
+	lb_typeIcon->setPixmap (icon);
+	
+	// Show a color edit dialog for the types that actually use the color
+	if (defaults->isColored ()) {
+		if (obj != null)
+			dColor = obj->dColor;
+		else
+			dColor = (type == LDObject::CondLine || type == LDObject::Line) ? edgecolor : maincolor;
+		
+		pb_color = new QPushButton;
+		setButtonBackground (pb_color, dColor);
+		connect (pb_color, SIGNAL (clicked ()), this, SLOT (slot_colorButtonClicked ()));
+	}
+	
+	for (short 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);
+	}
+	
+	IMPLEMENT_DIALOG_BUTTONS
+	
+	QGridLayout* const layout = new QGridLayout;
+	layout->addWidget (lb_typeIcon, 0, 0);
+	
+	switch (type) {
+	case LDObject::Line:
+	case LDObject::CondLine:
+	case LDObject::Triangle:
+	case LDObject::Quad:
+		// Apply coordinates
+		if (obj) {
+			for (short i = 0; i < coordCount / 3; ++i)
+			for (short j = 0; j < 3; ++j)
+				dsb_coords[(i * 3) + j]->setValue (obj->vaCoords[i].coord (j));
+		}
+		break;
+	
+	case LDObject::Comment:
+		layout->addWidget (le_comment, 0, 1);
+		break;
+	
+	case LDObject::BFC:
+		layout->addWidget (rb_bfcType, 0, 1);
+		break;
+	
+	case LDObject::Radial:
+		layout->addWidget (rb_radType, 1, 1, 3, 2);
+		layout->addWidget (cb_radHiRes, 1, 3);
+		layout->addWidget (lb_radSegments, 2, 3);
+		layout->addWidget (sb_radSegments, 2, 4);
+		layout->addWidget (lb_radRingNum, 3, 3);
+		layout->addWidget (sb_radRingNum, 3, 4);
+		
+		if (obj)
+			for (short i = 0; i < 3; ++i)
+				dsb_coords[i]->setValue (static_cast<LDRadial*> (obj)->vPosition.coord (i));
+		break;
+	
+	case LDObject::Subfile:
+		layout->addWidget (tw_subfileList, 1, 1, 1, 2);
+		layout->addWidget (lb_subfileName, 2, 1);
+		layout->addWidget (le_subfileName, 2, 2);
+		
+		if (obj)
+			for (short i = 0; i < 3; ++i)
+				dsb_coords[i]->setValue (static_cast<LDSubfile*> (obj)->vPosition.coord (i));
+		break;
+	
+	default:
+		break;
+	}
+	
+	if (type == LDObject::Subfile || type == LDObject::Radial) {
+		QLabel* lb_matrix = new QLabel ("Matrix:");
+		le_matrix = new QLineEdit;
+		// le_matrix->setValidator (new QDoubleValidator);
+		matrix<3> defval = g_identity;
+		
+		if (obj) {
+			if (obj->getType () == LDObject::Subfile)
+				defval = static_cast<LDSubfile*> (obj)->mMatrix;
+			else
+				defval = static_cast<LDRadial*> (obj)->mMatrix;
+		}
+		
+		le_matrix->setText (defval.stringRep ());
+		layout->addWidget (lb_matrix, 4, 1);
+		layout->addWidget (le_matrix, 4, 2, 1, 3);
+	}
+	
+	if (defaults->isColored ())
+		layout->addWidget (pb_color, 1, 0);
+	
+	if (coordCount > 0) {
+		QGridLayout* const qCoordLayout = new QGridLayout;
+		
+		for (short i = 0; i < coordCount; ++i)
+			qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3));
+		
+		layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3);
+	}
+	
+	layout->addWidget (bbx_buttons, 5, 0, 1, 4);
+	setLayout (layout);
+	setWindowTitle (fmt (APPNAME ": New %s",
+		g_saObjTypeNames[type]).chars());
+	
+	setWindowIcon (icon);
+	delete defaults;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void AddObjectDialog::setButtonBackground (QPushButton* button, short color) {
+	button->setIcon (getIcon ("palette"));
+	button->setAutoFillBackground (true);
+	button->setStyleSheet (
+		fmt ("background-color: %s", getColor (color)->zColorString.chars()).chars()
+	);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+char* AddObjectDialog::currentSubfileName() {
+	SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem ());
+	
+	if (item->subfileID == -1)
+		return null; // selected a heading
+	
+	return g_PartList[item->subfileID].sName;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void AddObjectDialog::slot_colorButtonClicked () {
+	ColorSelectDialog::staticDialog (dColor, dColor, this);
+	setButtonBackground (pb_color, dColor);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void AddObjectDialog::slot_radialTypeChanged (int dType) {
+	LDRadial::Type eType = (LDRadial::Type) dType;
+	sb_radRingNum->setEnabled (eType == LDRadial::Ring || eType == LDRadial::Cone);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void AddObjectDialog::slot_subfileTypeChanged () {
+	char* name = currentSubfileName ();
+	
+	if (name)
+		le_subfileName->setText (name);
+}
+
+// =============================================================================
+template<class T> T* initObj (LDObject*& obj) {
+	if (obj == null)
+		obj = new T;
+	
+	return static_cast<T*> (obj);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj) {
+	setlocale (LC_ALL, "C");
+	
+	// Redirect editing of gibberish to the set contents dialog
+	if (obj && obj->getType () == LDObject::Gibberish) {
+		SetContentsDialog::staticDialog (obj);
+		return;
+	}
+	
+	if (type == LDObject::Empty)
+		return; // Nothing to edit with empties
+	
+	const bool newObject = (obj == null);
+	AddObjectDialog dlg (type, obj);
+	
+	if (obj)
+		assert (obj->getType () == type);
+	
+	if (dlg.exec () == false)
+		return;
+	
+	LDObject* backup = null;
+	if (!newObject)
+		backup = obj->clone ();
+	
+	matrix<3> transform = g_identity;
+	if (type == LDObject::Subfile || type == LDObject::Radial) {
+		vector<str> matrixstrvals = str (dlg.le_matrix->text ()).split (" ", true);
+		
+		if (matrixstrvals.size () == 9) {
+			double matrixvals[9];
+			int i = 0;
+			
+			for (str val : matrixstrvals)
+				matrixvals[i++] = atof (val);
+			
+			transform = matrix<3> (matrixvals);
+		}
+	}
+	
+	switch (type) {
+	case LDObject::Comment:
+		{
+			LDComment* comm = initObj<LDComment> (obj);
+			comm->text = dlg.le_comment->text ();
+		}
+		break;
+	
+	case LDObject::Line:
+		{
+			LDLine* line = initObj<LDLine> (obj);
+			line->dColor = dlg.dColor;
+			APPLY_COORDS (line, 2)
+		}
+		break;
+	
+	case LDObject::Triangle:
+		{
+			LDTriangle* tri = initObj<LDTriangle> (obj);
+			tri->dColor = dlg.dColor;
+			APPLY_COORDS (tri, 3)
+		}
+		break;
+	
+	case LDObject::Quad:
+		{
+			LDQuad* quad = initObj<LDQuad> (obj);
+			quad->dColor = dlg.dColor;
+			APPLY_COORDS (quad, 4)
+		}
+		break;
+	
+	case LDObject::CondLine:
+		{
+			LDCondLine* line = initObj<LDCondLine> (obj);
+			line->dColor = dlg.dColor;
+			APPLY_COORDS (line, 4)
+		}
+		break;
+	
+	case LDObject::BFC:
+		{
+			LDBFC* bfc = initObj<LDBFC> (obj);
+			bfc->type = (LDBFC::Type) dlg.rb_bfcType->value ();
+		}
+		break;
+	
+	case LDObject::Vertex:
+		{
+			LDVertex* vert = initObj<LDVertex> (obj);
+			vert->dColor = dlg.dColor;
+			
+			for (const Axis ax : g_Axes)
+				vert->vPosition[ax] = dlg.dsb_coords[ax]->value ();
+		}
+		break;
+	
+	case LDObject::Radial:
+		{
+			LDRadial* pRad = initObj<LDRadial> (obj);
+			pRad->dColor = dlg.dColor;
+			
+			for (const Axis ax : g_Axes)
+				pRad->vPosition[ax] = dlg.dsb_coords[ax]->value ();
+			
+			pRad->dDivisions = (dlg.cb_radHiRes->checkState () != Qt::Checked) ? 16 : 48;
+			pRad->dSegments = min<short> (dlg.sb_radSegments->value (), pRad->dDivisions);
+			pRad->eRadialType = (LDRadial::Type) dlg.rb_radType->value ();
+			pRad->dRingNum = dlg.sb_radRingNum->value ();
+			pRad->mMatrix = transform;
+		}
+		break;
+	
+	case LDObject::Subfile:
+		{
+			str name = dlg.le_subfileName->text ();
+			if (~name == 0)
+				return; // no subfile filename
+			
+			OpenFile* file = loadSubfile (name);
+			if (!file)
+				return;
+			
+			LDSubfile* ref = initObj<LDSubfile> (obj);
+			ref->dColor = dlg.dColor;
+			
+			for (const Axis ax : g_Axes)
+				ref->vPosition[ax] = dlg.dsb_coords[ax]->value ();
+			
+			ref->zFileName = name;
+			ref->mMatrix = transform;
+			ref->pFile = file;
+		}
+		break;
+	
+	default:
+		break;
+	}
+	
+	if (newObject) {
+		ulong idx = g_win->getInsertionPoint ();
+		g_curfile->insertObj (idx, obj);
+		History::addEntry (new AddHistory ({(ulong) idx}, {obj->clone ()}));
+	} else {
+		History::addEntry (new EditHistory ({(ulong) obj->getIndex (g_curfile)}, {backup}, {obj->clone ()}));
+	}
+	
+	g_win->refresh ();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/addObjectDialog.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,83 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ZZ_ADDOBJECTDIALOG_H
+#define ZZ_ADDOBJECTDIALOG_H
+
+#include "gui.h"
+#include "radiobox.h"
+#include <qdialog.h>
+#include <qlineedit.h>
+#include <qdialogbuttonbox.h>
+#include <qcheckbox.h>
+#include <qspinbox.h>
+#include <qlabel.h>
+#include <qradiobutton.h>
+#include <qlistwidget.h>
+#include <qtreewidget.h>
+
+class AddObjectDialog : public QDialog {
+	Q_OBJECT
+	
+public:
+    AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent = null);
+	static void staticDialog (const LDObject::Type type, LDObject* obj);
+	
+	QLabel* lb_typeIcon;
+	
+	// Comment line edit
+	QLineEdit* le_comment;
+	
+	// Coordinate edits for.. anything with coordinates, really.
+	QDoubleSpinBox* dsb_coords[12];
+	
+	// Color selection dialog button
+	QPushButton* pb_color;
+	
+	// BFC-related widgets
+	RadioBox* rb_bfcType;
+	
+	// Subfile stuff
+	QTreeWidget* tw_subfileList;
+	QLineEdit* le_subfileName;
+	QLabel* lb_subfileName;
+	
+	// Radial stuff
+	QCheckBox* cb_radHiRes;
+	RadioBox* rb_radType;
+	QSpinBox* sb_radSegments, *sb_radRingNum;
+	QLabel* lb_radType, *lb_radResolution, *lb_radSegments,
+		*lb_radRingNum;
+	
+	QLineEdit* le_matrix;
+	
+	QDialogButtonBox* bbx_buttons;
+	
+private:
+	void setButtonBackground (QPushButton* button, short color);
+	char* currentSubfileName ();
+	
+	short dColor;
+	
+private slots:
+	void slot_colorButtonClicked ();
+	void slot_radialTypeChanged (int type);
+	void slot_subfileTypeChanged ();
+};
+
+#endif // ZZ_ADDOBJECTDIALOG_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bbox.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,167 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+#include "bbox.h"
+#include "ldtypes.h"
+#include "file.h"
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+bbox::bbox () {
+	reset ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void bbox::calculate () {
+	reset ();
+	
+	if (!g_curfile)
+		return;
+	
+	for (LDObject* obj : g_curfile->m_objs)
+		calcObject (obj);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void bbox::calcObject (LDObject* obj) {
+	switch (obj->getType ()) {
+	case LDObject::Line:
+		{
+			LDLine* line = static_cast<LDLine*> (obj);
+			for (short i = 0; i < 2; ++i)
+				calcVertex (line->vaCoords[i]);
+		}
+		break;
+	
+	case LDObject::Triangle:
+		{
+			LDTriangle* tri = static_cast<LDTriangle*> (obj);
+			for (short i = 0; i < 3; ++i)
+				calcVertex (tri->vaCoords[i]);
+		}
+		break;
+	
+	case LDObject::Quad:
+		{
+			LDQuad* quad = static_cast<LDQuad*> (obj);
+			for (short i = 0; i < 4; ++i)
+				calcVertex (quad->vaCoords[i]);
+		}
+		break;
+	
+	case LDObject::CondLine:
+		{
+			LDCondLine* line = static_cast<LDCondLine*> (obj);
+			for (short i = 0; i < 4; ++i)
+				calcVertex (line->vaCoords[i]);
+		}
+		break;
+	
+	case LDObject::Subfile:
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			vector<LDObject*> objs = ref->inlineContents (true, true);
+			
+			for (LDObject* obj : objs) {
+				calcObject (obj);
+				delete obj;
+			}
+		}
+		break;
+	
+	case LDObject::Radial:
+		{
+			LDRadial* rad = static_cast<LDRadial*> (obj);
+			vector<LDObject*> objs = rad->decompose (true);
+			
+			for (LDObject* obj : objs) {
+				calcObject (obj);
+				delete obj;
+			}
+		}
+		break;
+	
+	default:
+		break;
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void bbox::calcVertex (vertex v) {
+	for (const Axis ax : g_Axes) {
+		if (v[ax] < m_v0[ax])
+			m_v0[ax] = v[ax];
+		
+		if (v[ax] > m_v1[ax])
+			m_v1[ax] = v[ax];
+	}
+	
+	m_empty = false;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void bbox::reset () {
+	m_v0[X] = m_v0[Y] = m_v0[Z] = +0x7FFFFFFF;
+	m_v1[X] = m_v1[Y] = m_v1[Z] = -0x7FFFFFFF;
+	
+	m_empty = true;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+double bbox::size () const {
+	double fXScale = (m_v0[X] - m_v1[X]);
+	double fYScale = (m_v0[Y] - m_v1[Y]);
+	double fZScale = (m_v0[Z] - m_v1[Z]);
+	double fSize = fZScale;
+	
+	if (fXScale > fYScale) {
+		if (fXScale > fZScale)
+			fSize = fXScale;
+	} else if (fYScale > fZScale)
+		fSize = fYScale;
+	
+	if (abs (fSize) >= 2.0f)
+		return abs (fSize / 2);
+	
+	return 1.0f;
+}
+
+// =============================================================================
+vertex bbox::center () const {
+	return vertex (
+		(m_v0[X] + m_v1[X]) / 2,
+		(m_v0[Y] + m_v1[Y]) / 2,
+		(m_v0[Z] + m_v1[Z]) / 2);
+}
+
+// =============================================================================
+bool bbox::empty() const {
+	return m_empty;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bbox.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,61 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BBOX_H
+#define BBOX_H
+
+#include "common.h"
+#include "types.h"
+
+// =============================================================================
+// bbox
+//
+// The bounding box is the box that encompasses a given set of objects. The
+// global instance g_BBox is the bbox for the model we have open.
+// v0 is the minimum vertex, v1 is the maximum vertex.
+// =============================================================================
+class bbox {
+public:
+	bbox ();
+	void reset ();
+	void calculate ();
+	double size () const;
+	void calcObject (LDObject* obj);
+	void calcVertex (vertex v);
+	vertex center () const;
+	bool empty () const;
+	
+	bbox& operator<< (LDObject* obj) {
+		calcObject (obj);
+		return *this;
+	}
+	
+	bbox& operator<< (vertex& v) {
+		calcVertex (v);
+		return *this;
+	}
+	
+	const vertex& v0 () { return m_v0; }
+	const vertex& v1 () { return m_v1; }
+	
+private:
+	vertex m_v0, m_v1;
+	bool m_empty;
+};
+
+#endif // BBOX_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colorSelectDialog.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,179 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+#include "gui.h"
+#include <qgraphicsscene.h>
+#include <qgraphicsview.h>
+#include <qicon.h>
+#include <qboxlayout.h>
+#include <qgraphicsitem.h>
+#include <qevent.h>
+#include <qscrollbar.h>
+#include "colorSelectDialog.h"
+#include "colors.h"
+#include "config.h"
+#include "misc.h"
+
+static const short g_dNumColumns = 8;
+static const short g_dNumRows = 10;
+static const short g_dSquareSize = 32;
+static const long g_lWidth = (g_dNumColumns * g_dSquareSize);
+static const long g_lHeight = (g_dNumRows * g_dSquareSize);
+static const long g_lMaxHeight = ((MAX_COLORS / g_dNumColumns) * g_dSquareSize);
+
+extern_cfg (str, gl_maincolor);
+extern_cfg (float, gl_maincolor_alpha);
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+ColorSelectDialog::ColorSelectDialog (short int defval, QWidget* parent) : QDialog (parent) {
+	// Remove the default color if it's invalid
+	if (!getColor (defval))
+		defval = -1;
+	
+	gs_scene = new QGraphicsScene;
+	gv_view = new QGraphicsView (gs_scene);
+	selColor = defval;
+	
+	// not really an icon but eh
+	gs_scene->setBackgroundBrush (getIcon ("checkerboard"));
+	
+	gs_scene->setSceneRect (0, 0, g_lWidth, g_lMaxHeight);
+	gv_view->setSceneRect (0, 0, g_lWidth, g_lMaxHeight);
+	
+	drawScene ();
+	
+	IMPLEMENT_DIALOG_BUTTONS
+	
+	// Set the size of the view
+	const long lWidth = g_lWidth + 21; // HACK
+	gv_view->setMaximumWidth (lWidth);
+	gv_view->setMinimumWidth (lWidth);
+	gv_view->setMaximumHeight (g_lHeight);
+	gv_view->setMinimumHeight (g_lHeight);
+	gv_view->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
+	
+	// If we have a default color selected, scroll down so that it is visible.
+	// TODO: find a better way to do this
+	if (defval >= ((g_dNumColumns * g_dNumRows) - 2)) {
+		ulong ulNewY = ((defval / g_dNumColumns) - 3) * g_dSquareSize;
+		gv_view->verticalScrollBar ()->setSliderPosition (ulNewY);
+	}
+	
+	lb_colorInfo = new QLabel;
+	drawColorInfo ();
+	
+	QVBoxLayout* qLayout = new QVBoxLayout;
+	qLayout->addWidget (gv_view);
+	qLayout->addWidget (lb_colorInfo);
+	qLayout->addWidget (bbx_buttons);
+	setLayout (qLayout);
+	
+	setWindowIcon (getIcon ("palette"));
+	setWindowTitle (APPNAME);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ColorSelectDialog::drawScene () {
+	const double fPenWidth = 1.0f;
+	QPen qPen (Qt::black, fPenWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
+	
+	// Draw the color rectangles.
+	gs_scene->clear ();
+	for (short i = 0; i < MAX_COLORS; ++i) {
+		color* meta = getColor (i);
+		if (!meta)
+			continue;
+		
+		const double x = (i % g_dNumColumns) * g_dSquareSize;
+		const double y = (i / g_dNumColumns) * g_dSquareSize;
+		const double w = (g_dSquareSize) - (fPenWidth / 2);
+		
+		QColor col = meta->qColor;
+		
+		if (i == maincolor) {
+			// Use the user preferences for main color here
+			col = gl_maincolor.value.chars ();
+			col.setAlpha (gl_maincolor_alpha * 255.0f);
+		}
+		
+		bool dark = (luma (col) < 80);
+		
+		gs_scene->addRect (x, y, w, w, qPen, col);
+		QGraphicsTextItem* qText = gs_scene->addText (fmt ("%lu", i).chars());
+		qText->setDefaultTextColor ((dark) ? Qt::white : Qt::black);
+		qText->setPos (x, y);
+		
+		if (i == selColor) {
+			auto curspic = gs_scene->addPixmap (getIcon ("colorcursor"));
+			curspic->setPos (x, y);
+		}
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ColorSelectDialog::drawColorInfo () {
+	color* col = getColor (selColor);
+	
+	if (selColor == -1 || !col) {
+		lb_colorInfo->setText ("---");
+		return;
+	}
+	
+	lb_colorInfo->setText (fmt ("%d - %s",
+		selColor, col->zName.chars()));
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ColorSelectDialog::mousePressEvent (QMouseEvent* event) {
+	QPointF qPoint = gv_view->mapToScene (event->pos ());
+	
+	ulong x = ((ulong)qPoint.x () - (g_dSquareSize / 2)) / g_dSquareSize;
+	ulong y = ((ulong)qPoint.y () - (g_dSquareSize / 2)) / g_dSquareSize;
+	ulong idx = (y * g_dNumColumns) + x;
+	
+	color* col = getColor (idx);
+	if (!col)
+		return;
+	
+	selColor = idx;
+	drawScene ();
+	drawColorInfo ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+bool ColorSelectDialog::staticDialog (short& val, short int defval, QWidget* parent) {
+	ColorSelectDialog dlg (defval, parent);
+	
+	if (dlg.exec () && dlg.selColor != -1) {
+		val = dlg.selColor;
+		return true;
+	}
+	
+	return false;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colorSelectDialog.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,49 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COLORSELECTOR_H
+#define COLORSELECTOR_H
+
+#include <qdialog.h>
+#include <qdialogbuttonbox.h>
+#include <qgraphicsscene.h>
+#include <qlabel.h>
+#include "common.h"
+
+class ColorSelectDialog : public QDialog {
+	Q_OBJECT
+	
+public:
+	explicit ColorSelectDialog (short defval = -1, QWidget* parent = null);
+	static bool staticDialog (short& val, short defval = -1, QWidget* parent = null);
+	
+	QGraphicsScene* gs_scene;
+	QGraphicsView* gv_view;
+	QLabel* lb_colorInfo;
+	QDialogButtonBox* bbx_buttons;
+	short selColor;
+	
+private:
+	void drawScene ();
+	void drawColorInfo ();
+	
+private slots:
+	void mousePressEvent (QMouseEvent* event);
+};
+
+#endif // COLORSELECTOR_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colors.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,173 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+#include "colors.h"
+#include "file.h"
+#include "misc.h"
+#include <qcolor.h>
+
+static color* g_LDColors[MAX_COLORS];
+static bool g_bColorsInit = false;
+
+void initColors () {
+	if (g_bColorsInit)
+		return;
+	
+	logf ("%s: initializing color information.\n", __func__);
+	
+	color* col;
+	
+	// Always make sure there's 16 and 24 available. They're special like that.
+	col = new color;
+	col->zColorString = "#AAAAAA";
+	col->qColor = col->zColorString.chars ();
+	col->qEdge = Qt::black;
+	g_LDColors[maincolor] = col;
+	
+	col = new color;
+	col->zColorString = "#000000";
+	col->qEdge = col->qColor = Qt::black;
+	g_LDColors[edgecolor] = col;
+	
+	parseLDConfig ();
+	
+	g_bColorsInit = true;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+color* getColor (short dColorNum) {
+	// Check bounds
+	if (dColorNum < 0 || dColorNum >= MAX_COLORS)
+		return null;
+	
+	return g_LDColors[dColorNum];
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static bool parseLDConfigTag (StringParser& pars, char const* sTag, str& zVal) {
+	short dPos;
+	if (!pars.findToken (dPos, sTag, 1))
+		return false;
+	
+	return pars.getToken (zVal, dPos + 1);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+short color::index () {
+	short idx = 0;
+	for (color* it : g_LDColors) {
+		if (it == this)
+			return idx;
+		idx++;
+	}
+	
+	return -1;
+}
+
+// =============================================================================
+uchar luma (QColor& col) {
+	return (0.2126f * col.red ()) +
+		(0.7152f * col.green ()) +
+		(0.0722f * col.blue ());
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void parseLDConfig () {
+	FILE* fp = openLDrawFile ("LDConfig.ldr", false);
+	
+	if (!fp)
+		return;
+	
+	// Even though LDConfig.ldr is technically an LDraw file, parsing it as one
+	// would be overkill by any standard.
+	char line[1024];
+	while (fgets (line, sizeof line, fp)) {
+		if (strlen (line) == 0 || line[0] != '0')
+			continue; // empty or illogical
+		
+		str zLine = line;
+		zLine.replace ("\n", "");
+		zLine.replace ("\r", "");
+		
+		StringParser pars (zLine, ' ');
+		short dCode = 0, dAlpha = 255;
+		str zName, zColor, zEdge, zValue;
+		
+		// Check 0 !COLOUR, parse the name
+		if (!pars.tokenCompare (0, "0") || !pars.tokenCompare (1, "!COLOUR") || !pars.getToken (zName, 2))
+			continue;
+		
+		// Replace underscores in the name with spaces for readability
+		zName.replace ("_", " ");
+		
+		// get the CODE tag
+		if (!parseLDConfigTag (pars, "CODE", zValue))
+			continue;
+		
+		// Ensure that the code is within range. must be within 0 - 512
+		dCode = atoi (zValue);
+		if (dCode < 0 || dCode >= 512)
+			continue;
+		
+		// Don't let LDConfig.ldr override the special colors 16 and 24. However,
+		// do take the name it gives for the color
+		if (dCode == maincolor || dCode == edgecolor) {
+			g_LDColors[dCode]->zName = zName;
+			continue;
+		}
+		
+		// VALUE tag
+		if (!parseLDConfigTag (pars, "VALUE", zColor))
+			continue;
+		
+		// EDGE tag
+		if (!parseLDConfigTag (pars, "EDGE", zEdge))
+			continue;
+		
+		// Ensure that our colors are correct
+		QColor qColor (zColor.chars()),
+			qEdge (zEdge.chars());
+		
+		if (!qColor.isValid () || !qEdge.isValid ())
+			continue;
+		
+		// Parse alpha if given.
+		if (parseLDConfigTag (pars, "ALPHA", zValue))
+			dAlpha = clamp<short> (atoi (zValue), 0, 255);
+		
+		color* col = new color;
+		col->zName = zName;
+		col->qColor = qColor;
+		col->qEdge = qEdge;
+		col->zColorString = zColor;
+		col->qColor.setAlpha (dAlpha);
+		
+		g_LDColors[dCode] = col;
+	}
+	
+	fclose (fp);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colors.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,48 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COLORS_H
+#define COLORS_H
+
+#include <qcolor.h>
+#include "common.h"
+
+#define MAX_COLORS 512
+
+class color {
+public:
+	str zName, zColorString;
+	QColor qColor, qEdge;
+	
+	short index ();
+};
+
+typedef struct {
+	const short dIndex;
+	const char* sName, *sColor;
+	const float fAlpha;
+} TemporaryColorMeta;
+
+void initColors ();
+void parseLDConfig ();
+uchar luma (QColor& col);
+
+// Safely gets a color with the given number or null if no such color.
+color* getColor (short dColorNum);
+
+#endif // COLORS_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/common.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,183 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// =============================================================================
+// This file is included one way or another in every source file of LDForge.
+// Stuff defined and included here is universally included.
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <vector>
+#include <stdint.h>
+#include <stdarg.h>
+#include "str.h"
+#include "config.h"
+#include "types.h"
+
+#define APPNAME "LDForge"
+
+#define VERSION_MAJOR 0
+#define VERSION_MAJOR_STR "0"
+#define VERSION_MINOR 1
+#define VERSION_MINOR_STR "1"
+
+// ============---
+// #define RELEASE
+
+// Version string identifier
+static const str versionString = fmt ("%d.%d", VERSION_MAJOR, VERSION_MINOR);
+
+#ifdef __GNUC__
+# define FORMAT_PRINTF(M,N) __attribute__ ((format (printf, M, N)))
+# define ATTR(N) __attribute__ ((N))
+#else
+# define FORMAT_PRINTF(M,N)
+# define ATTR(N)
+#endif // __GNUC__
+
+#ifdef WIN32
+#define DIRSLASH "\\"
+#else // WIN32
+#define DIRSLASH "/"
+#endif // WIN32
+
+#ifdef RELEASE
+#define NDEBUG // remove asserts
+#endif // RELEASE
+
+#ifdef null
+#undef null
+#endif // null
+
+// Null pointer
+static const std::nullptr_t null = nullptr;
+
+// Main and edge color identifiers
+static const short maincolor = 16;
+static const short edgecolor = 24;
+
+static const bool yup = true;
+static const bool nope = false;
+
+class ForgeWindow;
+class LDObject;
+class bbox;
+class OpenFile;
+class QApplication;
+
+// =============================================================================
+// Plural expression
+#define PLURAL(n) ((n != 1) ? "s" : "")
+
+// -----------------------------------------------------------------------------
+// Shortcut for formatting
+#define PERFORM_FORMAT(in, out) \
+	va_list v; \
+	va_start (v, in); \
+	char* out = vdynformat (in, v, 256); \
+	va_end (v);
+
+// -----------------------------------------------------------------------------
+// Shortcuts for stuffing vertices into printf-formatting.
+#define FMT_VERTEX "(%.3f, %.3f, %.3f)"
+#define FVERTEX(V) V.x, V.y, V.z
+
+// -----------------------------------------------------------------------------
+// Templated clamp
+template<class T> static inline T clamp (T a, T min, T max) {
+	return (a > max) ? max : (a < min) ? min : a;
+}
+
+// Templated minimum
+template<class T> static inline T min (T a, T b) {
+	return (a < b) ? a : b;
+}
+
+// Templated maximum
+template<class T> static inline T max (T a, T b) {
+	return (a > b) ? a : b;
+}
+
+// Templated absolute value
+template<class T> static inline T abs (T a) {
+	return (a >= 0) ? a : -a;
+}
+
+// Quick QString to const char* conversion
+static inline const char* qchars (QString qstr) {
+	return qstr.toStdString ().c_str ();
+}
+
+static const double pi = 3.14159265358979323846f;
+
+#ifdef IN_IDE_PARSER // KDevelop workaround
+// Current function name
+static const char* __func__ = "";
+#endif // IN_IDE_PARSER
+
+// -----------------------------------------------------------------------------
+enum LogType {
+	LOG_Normal,
+	LOG_Warning,
+	LOG_Error,
+	LOG_Dev,
+};
+
+// logf - universal access to the message log. Defined here so that I don't have
+// to include gui.h here and recompile everything every time that file changes.
+// logf is defined in main.cpp
+void logf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2);
+void logf (LogType type, const char* fmtstr, ...) FORMAT_PRINTF (2, 3);
+void warnf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2);
+void errf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2);
+
+#ifndef RELEASE
+void devf (const char* fmtstr, ...);
+#else
+# define devf(...)
+#endif // RELEASE
+
+// -----------------------------------------------------------------------------
+// Vertex at (0, 0, 0)
+extern const vertex g_origin;
+
+// -----------------------------------------------------------------------------
+// Pointer to the OpenFile which is currently being edited by the user.
+extern OpenFile* g_curfile;
+
+// -----------------------------------------------------------------------------
+// Pointer to the bounding box.
+extern bbox g_BBox;
+
+// -----------------------------------------------------------------------------
+// Vector of all currently opened files.
+extern vector<OpenFile*> g_loadedFiles;
+
+// -----------------------------------------------------------------------------
+// Pointer to the main application.
+extern const QApplication* g_app;
+
+// -----------------------------------------------------------------------------
+// Identity matrix
+extern const matrix<3> g_identity;
+
+#endif // COMMON_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/config.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,276 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <QDir>
+#include "common.h"
+#include "str.h"
+#include "config.h"
+
+std::vector<config*> g_configPointers;
+
+// =============================================================================
+const char* g_WeekdayNames[7] = {
+	"Sunday",
+	"Monday",
+	"Tuesday",
+	"Wednesday",
+	"Thursday",
+	"Friday",
+	"Saturday",
+};
+
+// =============================================================================
+static const char* g_MonthNames[12] = {
+	"Januray",
+	"February",
+	"March",
+	"April",
+	"May",
+	"June",
+	"July",
+	"August",
+	"September",
+	"October",
+	"November"
+	"December",
+};
+
+static const char* g_ConfigTypeNames[] = {
+	"None",
+	"Integer",
+	"String",
+	"Float",
+	"Boolean",
+	"Key sequence",
+};
+
+// =============================================================================
+// Load the configuration from file
+bool config::load () {
+	printf ("config::load: loading configuration file.\n");
+	
+	// Locale must be disabled for atof
+	setlocale (LC_NUMERIC, "C");
+	
+	FILE* fp = fopen (filepath().chars(), "r");
+	char linedata[MAX_INI_LINE];
+	char* line;
+	size_t ln = 0;
+	
+	if (!fp)
+		return false; // can't open for reading
+	
+	// Read the values.
+	while (fgets (linedata, MAX_INI_LINE, fp)) {
+		ln++;
+		line = linedata;
+		
+		while (*line != 0 && (*line <= 32 || *line >= 127))
+			line++; // Skip junk
+		
+		if (*line == '\0' || line[0] == '#')
+			continue; // Empty line or comment.
+		
+		// Find the equals sign.
+		char* equals = strchr (line, '=');
+		if (!equals) {
+			fprintf (stderr, "couldn't find `=` sign in entry `%s`\n", line);
+			continue;
+		}
+		
+		str entry = str (line).substr (0, equals - line);
+		
+		// Find the config entry for this.
+		config* cfg = null;
+		for (config* i : g_configPointers)
+			if (entry == i->name)
+				cfg = i;
+		
+		if (!cfg) {
+			fprintf (stderr, "unknown config `%s`\n", entry.chars());
+			continue;
+		}
+		
+		str valstring = str (line).substr (equals - line + 1, -1);
+		
+		// Trim the crap off the end
+		while (~valstring) {
+			char c = valstring[~valstring - 1];
+			if (c <= 32 || c >= 127)
+				valstring -= 1;
+			else
+				break;
+		}
+		
+		switch (cfg->getType()) {
+		case CONFIG_int:
+			static_cast<intconfig*> (cfg)->value = atoi (valstring.chars());
+			break;
+		
+		case CONFIG_str:
+			static_cast<strconfig*> (cfg)->value = valstring;
+			break;
+		
+		case CONFIG_float:
+			static_cast<floatconfig*> (cfg)->value = atof (valstring.chars());
+			break;
+		
+		case CONFIG_bool:
+		{
+			bool& val = static_cast<boolconfig*> (cfg)->value;
+			
+			if (+valstring == "TRUE" || valstring == "1")
+				val = true;
+			else if (+valstring == "FALSE" || valstring == "0")
+				val = false;
+			break;
+		}
+		
+		case CONFIG_keyseq:
+			static_cast<keyseqconfig*> (cfg)->value = keyseq::fromString (valstring.chars ());
+			break;
+		
+		default:
+			break;
+		}
+	}
+	
+	fclose (fp);
+	return true;
+}
+
+// =============================================================================
+// Write a given formatted string to the given file stream
+static size_t writef (FILE* fp, const char* fmt, ...) {
+	va_list va;
+	
+	va_start (va, fmt);
+	char* buf = vdynformat (fmt, va, 256);
+	va_end (va);
+	
+	size_t len = fwrite (buf, 1, strlen (buf), fp);
+	delete[] buf;
+	
+	return len;
+}
+
+// =============================================================================
+// Save the configuration to disk
+bool config::save () {
+	// The function will write floats, disable the locale now so that they
+	// are written properly.
+	setlocale (LC_NUMERIC, "C");
+	
+	// If the directory doesn't exist, create it now.
+	if (QDir (dirpath ()).exists () == nope) {
+		fprintf (stderr, "Creating config path %s...\n", dirpath().chars());
+		if (!QDir ().mkpath (dirpath().chars())) {
+			logf (LOG_Warning, "Failed to create the directory. Configuration cannot be saved!\n");
+			return false; // Couldn't create directory
+		}
+	}
+	
+	FILE* fp = fopen (filepath().chars(), "w");
+	printf ("writing cfg to %s\n", filepath().chars());
+	
+	if (!fp) {
+		printf ("Couldn't open %s for writing\n", filepath().chars());
+		return false;
+	}
+	
+	const time_t curtime = time (NULL);
+	const struct tm* timeinfo = localtime (&curtime);
+	const char* daysuffix =
+		(timeinfo->tm_mday % 10 == 1) ? "st" :
+		(timeinfo->tm_mday % 10 == 2) ? "nd" :
+		(timeinfo->tm_mday % 10 == 3) ? "rd" : "th";
+	
+	writef (fp, "# Configuration file for " APPNAME "\n");
+	writef (fp, "# Written on %s, %s %d%s %d %.2d:%.2d:%.2d\n",
+		g_WeekdayNames[timeinfo->tm_wday], g_MonthNames[timeinfo->tm_mon],
+		timeinfo->tm_mday, daysuffix, timeinfo->tm_year + 1900,
+		timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
+	
+	for (config* cfg : g_configPointers) {
+		str valstring;
+		switch (cfg->getType()) {
+		case CONFIG_int:
+			valstring.format ("%d", static_cast<intconfig*> (cfg)->value);
+			break;
+		case CONFIG_str:
+			valstring = static_cast<strconfig*> (cfg)->value;
+			break;
+		case CONFIG_float:
+			valstring.format ("%f", static_cast<floatconfig*> (cfg)->value);
+			
+			// Trim any trailing zeros
+			if (valstring.first (".") != -1) {
+				while (valstring[~valstring - 1] == '0')
+					valstring -= 1;
+				
+				// But don't trim the only one out...
+				if (valstring[~valstring - 1] == '.')
+					valstring += '0';
+			}
+			
+			break;
+		case CONFIG_bool:
+			valstring = (static_cast<boolconfig*> (cfg)->value) ? "true" : "false";
+			break;
+		case CONFIG_keyseq:
+			valstring = static_cast<keyseqconfig*> (cfg)->value.toString ();
+			break;
+		default:
+			break;
+		}
+		
+		const char* sDefault = (cfg->getType() != CONFIG_keyseq) ? cfg->defaultstring :
+			static_cast<keyseqconfig*> (cfg)->defval.toString ().toUtf8 ().constData ();
+		
+		// Write the entry now.
+		writef (fp, "\n# [%s] default: %s\n", g_ConfigTypeNames[cfg->getType()], sDefault);
+		writef (fp, "%s=%s\n", cfg->name, valstring.chars());
+	}
+	
+	fclose (fp);
+	return true;
+}
+
+// =============================================================================
+void config::reset () {
+	for (size_t i = 0; i < NUM_CONFIG; i++)
+		g_configPointers[i]->resetValue ();
+}
+
+// =============================================================================
+str config::filepath () {
+	str path;
+	path.format ("%s%s.cfg", dirpath ().chars (),
+		str (APPNAME).tolower ().chars ());
+	return path;
+}
+
+// =============================================================================
+str config::dirpath () {
+	return fmt ("%s/.%s/", qchars (QDir::homePath ()),
+		str (APPNAME).tolower ().chars ());
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/config.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,227 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include "common.h"
+#include "str.h"
+
+// =============================================================================
+#include <QString>
+#include <qkeysequence.h>
+
+#define MAX_INI_LINE 512
+#define NUM_CONFIG (g_pConfigPointers.size ())
+
+#define cfg(T, NAME, DEFAULT) \
+	T##config NAME (DEFAULT, #NAME, #T, #DEFAULT)
+
+#define extern_cfg(T, NAME) \
+	extern T##config NAME
+
+// =============================================================================
+enum configtype_e {
+	CONFIG_none,
+	CONFIG_int,
+	CONFIG_str,
+	CONFIG_float,
+	CONFIG_bool,
+	CONFIG_keyseq,
+};
+
+// =========================================================
+class config {
+public:
+	const char* name, *typestring, *defaultstring;
+	
+	virtual configtype_e getType () {
+		return CONFIG_none;
+	}
+	
+	virtual void resetValue () {}
+	
+	// ------------------------------------------
+	static bool load ();
+	static bool save ();
+	static void reset ();
+	static str dirpath ();
+	static str filepath ();
+};
+
+extern std::vector<config*> g_configPointers;
+
+// =============================================================================
+#define DEFINE_UNARY_OPERATOR(T, OP) \
+	T operator OP () { \
+		return (OP value); \
+	} \
+
+#define DEFINE_BINARY_OPERATOR(T, OP) \
+	T operator OP (const T other) { \
+		return (value OP other); \
+	} \
+
+#define DEFINE_ASSIGN_OPERATOR(T, OP) \
+	T& operator OP (const T other) { \
+		return (value OP other); \
+	} \
+
+#define DEFINE_COMPARE_OPERATOR(T, OP) \
+	bool operator OP (const T other) { \
+		return (value OP other); \
+	} \
+
+#define DEFINE_CAST_OPERATOR(T) \
+	operator T () { \
+		return (T) value; \
+	} \
+
+#define DEFINE_ALL_COMPARE_OPERATORS(T) \
+	DEFINE_COMPARE_OPERATOR (T, ==) \
+	DEFINE_COMPARE_OPERATOR (T, !=) \
+	DEFINE_COMPARE_OPERATOR (T, >) \
+	DEFINE_COMPARE_OPERATOR (T, <) \
+	DEFINE_COMPARE_OPERATOR (T, >=) \
+	DEFINE_COMPARE_OPERATOR (T, <=) \
+
+#define DEFINE_INCREMENT_OPERATORS(T) \
+	T operator++ () {return ++value;} \
+	T operator++ (int) {return value++;} \
+	T operator-- () {return --value;} \
+	T operator-- (int) {return value--;}
+
+#define CONFIGTYPE(T) \
+class T##config : public config
+
+#define IMPLEMENT_CONFIG(T) \
+	T value, defval; \
+	\
+	T##config (T _defval, const char* _name, const char* _typestring, \
+		const char* _defaultstring) \
+	{ \
+		value = defval = _defval; \
+		name = _name; \
+		typestring = _typestring; \
+		defaultstring = _defaultstring; \
+		g_pConfigPointers.push_back (this); \
+	} \
+	operator T () { \
+		return value; \
+	} \
+	configtype_e getType () { \
+		return CONFIG_##T; \
+	} \
+	virtual void resetValue () { \
+		value = defval; \
+	}
+
+// =============================================================================
+CONFIGTYPE (int) {
+public:
+	IMPLEMENT_CONFIG (int)
+	
+	// Int-specific operators
+	DEFINE_ALL_COMPARE_OPERATORS (int)
+	DEFINE_INCREMENT_OPERATORS (int)
+	DEFINE_BINARY_OPERATOR (int, +)
+	DEFINE_BINARY_OPERATOR (int, -)
+	DEFINE_BINARY_OPERATOR (int, *)
+	DEFINE_BINARY_OPERATOR (int, /)
+	DEFINE_BINARY_OPERATOR (int, %)
+	DEFINE_BINARY_OPERATOR (int, ^)
+	DEFINE_BINARY_OPERATOR (int, |)
+	DEFINE_BINARY_OPERATOR (int, &)
+	DEFINE_BINARY_OPERATOR (int, >>)
+	DEFINE_BINARY_OPERATOR (int, <<)
+	DEFINE_UNARY_OPERATOR (int, !)
+	DEFINE_UNARY_OPERATOR (int, ~)
+	DEFINE_UNARY_OPERATOR (int, -)
+	DEFINE_UNARY_OPERATOR (int, +)
+	DEFINE_ASSIGN_OPERATOR (int, =)
+	DEFINE_ASSIGN_OPERATOR (int, +=)
+	DEFINE_ASSIGN_OPERATOR (int, -=)
+	DEFINE_ASSIGN_OPERATOR (int, *=)
+	DEFINE_ASSIGN_OPERATOR (int, /=)
+	DEFINE_ASSIGN_OPERATOR (int, %=)
+	DEFINE_ASSIGN_OPERATOR (int, >>=)
+	DEFINE_ASSIGN_OPERATOR (int, <<=)
+};
+
+// =============================================================================
+CONFIGTYPE (str) {
+public:
+	IMPLEMENT_CONFIG (str)
+	
+	DEFINE_ALL_COMPARE_OPERATORS (str)
+	DEFINE_BINARY_OPERATOR (str, -)
+	DEFINE_BINARY_OPERATOR (str, *)
+	DEFINE_UNARY_OPERATOR (str, !)
+	DEFINE_ASSIGN_OPERATOR (str, =)
+	DEFINE_ASSIGN_OPERATOR (str, +=)
+	DEFINE_ASSIGN_OPERATOR (str, -=)
+	DEFINE_ASSIGN_OPERATOR (str, *=)
+	DEFINE_CAST_OPERATOR (char*)
+	
+	char operator[] (size_t n) {
+		return value[n];
+	}
+	
+#ifdef CONFIG_WITH_QT
+	operator QString () {
+		return QString (value.chars());
+	}
+#endif // CONFIG_WITH_QT
+};
+
+// =============================================================================
+CONFIGTYPE (float) {
+public:
+	IMPLEMENT_CONFIG (float)
+	
+	DEFINE_ALL_COMPARE_OPERATORS (float)
+	DEFINE_INCREMENT_OPERATORS (float)
+	DEFINE_BINARY_OPERATOR (float, +)
+	DEFINE_BINARY_OPERATOR (float, -)
+	DEFINE_BINARY_OPERATOR (float, *)
+	DEFINE_UNARY_OPERATOR (float, !)
+	DEFINE_ASSIGN_OPERATOR (float, =)
+	DEFINE_ASSIGN_OPERATOR (float, +=)
+	DEFINE_ASSIGN_OPERATOR (float, -=)
+	DEFINE_ASSIGN_OPERATOR (float, *=)
+};
+
+// =============================================================================
+CONFIGTYPE (bool) {
+public:
+	IMPLEMENT_CONFIG (bool)
+	DEFINE_ALL_COMPARE_OPERATORS (bool)
+	DEFINE_ASSIGN_OPERATOR (bool, =)
+};
+
+// =============================================================================
+typedef QKeySequence keyseq;
+
+CONFIGTYPE (keyseq) {
+public:
+	IMPLEMENT_CONFIG (keyseq)
+	DEFINE_ALL_COMPARE_OPERATORS (keyseq)
+	DEFINE_ASSIGN_OPERATOR (keyseq, =)
+};
+
+#endif // CONFIG_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/configDialog.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,796 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+#include "configDialog.h"
+#include "file.h"
+#include "config.h"
+#include "misc.h"
+#include "colors.h"
+#include "colorSelectDialog.h"
+#include <qgridlayout.h>
+#include <qfiledialog.h>
+#include <qcolordialog.h>
+#include <qboxlayout.h>
+#include <qevent.h>
+#include <qgroupbox.h>
+
+extern_cfg (str, gl_bgcolor);
+extern_cfg (str, gl_maincolor);
+extern_cfg (bool, lv_colorize);
+extern_cfg (bool, gl_colorbfc);
+extern_cfg (float, gl_maincolor_alpha);
+extern_cfg (int, gl_linethickness);
+extern_cfg (int, gui_toolbar_iconsize);
+extern_cfg (str, gui_colortoolbar);
+extern_cfg (bool, gl_selflash);
+extern_cfg (bool, edit_schemanticinline);
+extern_cfg (bool, gl_blackedges);
+
+ConfigDialog* g_ConfigDialog = null;
+
+#define INIT_CHECKBOX(BOX, CFG) \
+	BOX->setCheckState (CFG ? Qt::Checked : Qt::Unchecked);
+
+#define APPLY_CHECKBOX(BTN, CFG) \
+	CFG = BTN->checkState() == Qt::Checked;
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+ConfigDialog::ConfigDialog (ForgeWindow* parent) : QDialog (parent) {
+	g_ConfigDialog = this;
+	tabs = new QTabWidget;
+	
+	initMainTab ();
+	initShortcutsTab ();
+	initQuickColorTab ();
+	initGridTab ();
+	initExtProgTab ();
+	
+	IMPLEMENT_DIALOG_BUTTONS
+	
+	QVBoxLayout* layout = new QVBoxLayout;
+	layout->addWidget (tabs);
+	layout->addWidget (bbx_buttons);
+	setLayout (layout);
+	
+	setWindowTitle ("Settings");
+	setWindowIcon (getIcon ("settings"));
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::initMainTab () {
+	mainTab = new QWidget;
+	
+	// =========================================================================
+	// Background and foreground colors
+	lb_viewBg = new QLabel ("Background color:");
+	pb_viewBg = new QPushButton;
+	setButtonBackground (pb_viewBg, gl_bgcolor.value);
+	connect (pb_viewBg, SIGNAL (clicked ()),
+		this, SLOT (slot_setGLBackground ()));
+	pb_viewBg->setWhatsThis ("This is the background color for the viewport.");
+	
+	lb_viewFg = new QLabel ("Foreground color:");
+	pb_viewFg = new QPushButton;
+	setButtonBackground (pb_viewFg, gl_maincolor.value);
+	connect (pb_viewFg, SIGNAL (clicked ()),
+		this, SLOT (slot_setGLForeground ()));
+	pb_viewFg->setWhatsThis ("This color is used for the main color.");
+	
+	// =========================================================================
+	// Alpha and line thickness sliders
+	lb_viewFgAlpha = new QLabel ("Alpha:");
+	makeSlider (sl_viewFgAlpha, 1, 10, (gl_maincolor_alpha * 10.0f));
+	sl_viewFgAlpha->setWhatsThis ("Opacity of main color in the viewport.");
+	
+	lb_lineThickness = new QLabel ("Line thickness:");
+	makeSlider (sl_lineThickness, 1, 8, gl_linethickness);
+	sl_lineThickness->setWhatsThis ("How thick lines should be drawn in the viewport.");
+	
+	// =========================================================================
+	// Tool bar icon size slider
+	lb_iconSize = new QLabel ("Toolbar icon size:");
+	makeSlider (sl_iconSize, 1, 5, (gui_toolbar_iconsize - 12) / 4);
+	
+	// =========================================================================
+	// List view colorizer and BFC red/green view checkboxes
+	cb_colorize = new QCheckBox ("Colorize polygons in object list");
+	cb_colorize->setChecked (lv_colorize);
+	cb_colorize->setWhatsThis ("Makes colored objects (non-16 and 24) appear "
+		"colored in the object list. A red polygon will have its description "
+		"written in red text.");
+	
+	cb_colorBFC = new QCheckBox ("Red/green BFC view");
+	cb_colorBFC->setChecked (gl_colorbfc);
+	cb_colorBFC->setWhatsThis ("Polygons' front sides become green and back "
+		"sides red. Not implemented yet.");
+	
+	cb_selFlash = new QCheckBox ("Selection flash");
+	cb_selFlash->setChecked (gl_selflash);
+	cb_colorBFC->setWhatsThis ("A pulse effect for clearer selection view.");
+	
+	cb_blackEdges = new QCheckBox ("Black edges");
+	cb_blackEdges->setWhatsThis ("Makes all edgelines appear black. If this is "
+		"not set, edge lines take their color as defined in LDConfig.ldr");
+	cb_blackEdges->setChecked (gl_blackedges);
+	
+	cb_schemanticInline = new QCheckBox ("Schemantic insertion only");
+	cb_schemanticInline->setChecked (edit_schemanticinline);
+	cb_colorBFC->setWhatsThis ("When inserting objects through inlining, file "
+		"inserting or through external programs, all non-schemantics (those without "
+		"actual meaning in the part file like comments and such) are filtered out.");
+	
+	cb_schemanticInline->setEnabled (false);
+	cb_colorBFC->setEnabled (false);
+	
+	QGridLayout* layout = new QGridLayout;
+	layout->addWidget (lb_viewBg, 0, 0);
+	layout->addWidget (pb_viewBg, 0, 1);
+	layout->addWidget (lb_viewFg, 0, 2);
+	layout->addWidget (pb_viewFg, 0, 3);
+	
+	layout->addWidget (lb_lineThickness, 1, 0);
+	layout->addWidget (sl_lineThickness, 1, 1);
+	layout->addWidget (lb_viewFgAlpha, 1, 2);
+	layout->addWidget (sl_viewFgAlpha, 1, 3);
+	
+	layout->addWidget (lb_iconSize, 2, 0);
+	layout->addWidget (sl_iconSize, 2, 1);
+	
+	layout->addWidget (cb_colorize, 3, 0, 1, 4);
+	layout->addWidget (cb_colorBFC, 4, 0, 1, 4);
+	layout->addWidget (cb_selFlash, 5, 0, 1, 4);
+	layout->addWidget (cb_blackEdges, 6, 0, 1, 4);
+	layout->addWidget (cb_schemanticInline, 7, 0, 1, 4);
+	mainTab->setLayout (layout);
+	
+	// Add the tab to the manager
+	tabs->addTab (mainTab, "Main settings");
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::initShortcutsTab () {
+	shortcutsTab = new QWidget;
+	lw_shortcutList = new QListWidget;
+	lw_shortcutList->setAlternatingRowColors (true);
+	
+	shortcutsTab->setWhatsThis ("Here you can alter keyboard shortcuts for "
+		"almost all LDForge actions. Only exceptions are the controls for the "
+		"viewport. Use the set button to set a key shortcut, clear to remove it "
+		"and reset to restore the shortcut to its default value.\n"
+		"\tShortcut changes apply immediately after closing this dialog." );
+	
+	// Init table items
+	ulong i = 0;
+	for (actionmeta& info : g_ActionMeta) {
+		QAction* const act = *info.qAct;
+		
+		ShortcutListItem* item = new ShortcutListItem;
+		setShortcutText (item, info);
+		item->setIcon (act->icon ());
+		item->setActionInfo (&info);
+		
+		// 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"));
+		
+		lw_shortcutList->insertItem (i++, item);
+	}
+	
+	lw_shortcutList->setSortingEnabled (true);
+	lw_shortcutList->sortItems ();
+	
+	pb_setShortcut = new QPushButton ("Set");
+	pb_resetShortcut = new QPushButton ("Reset");
+	pb_clearShortcut = new QPushButton ("Clear");
+	
+	connect (pb_setShortcut, SIGNAL (clicked ()), this, SLOT (slot_setShortcut ()));
+	connect (pb_resetShortcut, SIGNAL (clicked ()), this, SLOT (slot_resetShortcut ()));
+	connect (pb_clearShortcut, SIGNAL (clicked ()), this, SLOT (slot_clearShortcut ()));
+	
+	QVBoxLayout* buttonLayout = new QVBoxLayout;
+	buttonLayout->addWidget (pb_setShortcut);
+	buttonLayout->addWidget (pb_resetShortcut);
+	buttonLayout->addWidget (pb_clearShortcut);
+	buttonLayout->addStretch (10);
+	
+	QGridLayout* layout = new QGridLayout;
+	layout->addWidget (lw_shortcutList, 0, 0);
+	layout->addLayout (buttonLayout, 0, 1);
+	shortcutsTab->setLayout (layout);
+	tabs->addTab (shortcutsTab, "Shortcuts");
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::initQuickColorTab () {
+	quickColorTab = new QWidget;
+	
+	pb_addColor = new QPushButton (getIcon ("palette"), "Add");
+	pb_delColor = new QPushButton (getIcon ("delete"), "Remove");
+	pb_changeColor = new QPushButton (getIcon ("palette"), "Set");
+	pb_addColorSeparator = new QPushButton ("Add Separator");
+	pb_moveColorUp = new QPushButton (getIcon ("arrow-up"), "Move Up");
+	pb_moveColorDown = new QPushButton (getIcon ("arrow-down"), "Move Down");
+	pb_clearColors = new QPushButton (getIcon ("delete-all"), "Clear");
+	lw_quickColors = new QListWidget;
+	
+	quickColorMeta = parseQuickColorMeta ();
+	updateQuickColorList ();
+	
+	QVBoxLayout* buttonLayout = new QVBoxLayout;
+	buttonLayout->addWidget (pb_addColor);
+	buttonLayout->addWidget (pb_delColor);
+	buttonLayout->addWidget (pb_changeColor);
+	buttonLayout->addWidget (pb_addColorSeparator);
+	buttonLayout->addWidget (pb_moveColorUp);
+	buttonLayout->addWidget (pb_moveColorDown);
+	buttonLayout->addWidget (pb_clearColors);
+	buttonLayout->addStretch (1);
+	
+	connect (pb_addColor, SIGNAL (clicked ()), this, SLOT (slot_setColor ()));
+	connect (pb_delColor, SIGNAL (clicked ()), this, SLOT (slot_delColor ()));
+	connect (pb_changeColor, SIGNAL (clicked ()), this, SLOT (slot_setColor ()));
+	connect (pb_addColorSeparator, SIGNAL (clicked ()), this, SLOT (slot_addColorSeparator ()));
+	connect (pb_moveColorUp, SIGNAL (clicked ()), this, SLOT (slot_moveColor ()));
+	connect (pb_moveColorDown, SIGNAL (clicked ()), this, SLOT (slot_moveColor ()));
+	connect (pb_clearColors, SIGNAL (clicked ()), this, SLOT (slot_clearColors ()));
+	
+	QGridLayout* layout = new QGridLayout;
+	layout->addWidget (lw_quickColors, 0, 0);
+	layout->addLayout (buttonLayout, 0, 1);
+	
+	quickColorTab->setLayout (layout);
+	tabs->addTab (quickColorTab, "Quick Colors");
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::initGridTab () {
+	QWidget* tab = new QWidget;
+	QGridLayout* layout = new QGridLayout;
+	QVBoxLayout* l2 = new QVBoxLayout;
+	
+	QLabel* xlabel = new QLabel ("X"),
+		*ylabel = new QLabel ("Y"),
+		*zlabel = new QLabel ("Z"),
+		*anglabel = new QLabel ("Angle");
+	
+	short i = 1;
+	for (QLabel* label : std::initializer_list<QLabel*> ({xlabel, ylabel, zlabel, anglabel})) {
+		label->setAlignment (Qt::AlignCenter);
+		layout->addWidget (label, 0, i++);
+	}
+	
+	for (int i = 0; i < g_NumGrids; ++i) {
+		// Icon
+		lb_gridIcons[i] = new QLabel;
+		lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%s", str (g_GridInfo[i].name).tolower ().chars ())));
+		
+		// Text label
+		lb_gridLabels[i] = new QLabel (fmt ("%s:", g_GridInfo[i].name));
+		
+		QHBoxLayout* labellayout = new QHBoxLayout;
+		labellayout->addWidget (lb_gridIcons[i]);
+		labellayout->addWidget (lb_gridLabels[i]);
+		layout->addLayout (labellayout, i + 1, 0);
+		
+		// Add the widgets
+		for (int j = 0; j < 4; ++j) {
+			dsb_gridData[i][j] = new QDoubleSpinBox;
+			dsb_gridData[i][j]->setValue (g_GridInfo[i].confs[j]->value);
+			layout->addWidget (dsb_gridData[i][j], i + 1, j + 1);
+		}
+	}
+	
+	l2->addLayout (layout);
+	l2->addStretch (1);
+	
+	tab->setLayout (l2);
+	tabs->addTab (tab, "Grids");
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+extern_cfg (str, prog_ytruder);
+extern_cfg (str, prog_rectifier);
+extern_cfg (str, prog_intersector);
+extern_cfg (str, prog_isecalc);
+static const struct extProgInfo {
+	const char* const name, *iconname;
+	strconfig* const path;
+	mutable QLineEdit* input;
+	mutable QPushButton* setPathButton;
+} g_extProgInfo[] = {
+	{ "Ytruder", "ytruder", &prog_ytruder, null, null },
+	{ "Rectifier", "rectifier", &prog_rectifier, null, null },
+	{ "Intersector", "intersector", &prog_intersector, null, null },
+	{ "Isecalc", "isecalc", &prog_isecalc, null, null },
+};
+
+void ConfigDialog::initExtProgTab () {
+	QWidget* tab = new QWidget;
+	QGridLayout* pathsLayout = new QGridLayout;
+	QGroupBox* pathsBox = new QGroupBox ("Paths", this);
+	QVBoxLayout* layout = new QVBoxLayout (this);
+	
+	ulong row = 0;
+	for (const extProgInfo& info : g_extProgInfo) {
+		QLabel* icon = new QLabel,
+			*progLabel = new QLabel (info.name);
+		QLineEdit* input = new QLineEdit;
+		QPushButton* setPathButton = new QPushButton ();
+		
+		icon->setPixmap (getIcon (info.iconname));
+		input->setText (info.path->value);
+		setPathButton->setIcon (getIcon ("folder"));
+		info.input = input;
+		info.setPathButton = setPathButton;
+		
+		connect (setPathButton, SIGNAL (clicked ()), this, SLOT (slot_setExtProgPath ()));
+		
+		pathsLayout->addWidget (icon, row, 0);
+		pathsLayout->addWidget (progLabel, row, 1);
+		pathsLayout->addWidget (input, row, 2);
+		pathsLayout->addWidget (setPathButton, row, 3);
+		++row;
+	}
+	
+	pathsBox->setLayout (pathsLayout);
+	layout->addWidget (pathsBox);
+	layout->addSpacing (10);
+	
+	tab->setLayout (layout);
+	tabs->addTab (tab, "Ext. Programs");
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::updateQuickColorList (quickColorMetaEntry* pSel) {
+	for (QListWidgetItem* qItem : quickColorItems)
+		delete qItem;
+	
+	quickColorItems.clear ();
+	
+	// Init table items
+	for (quickColorMetaEntry& entry : quickColorMeta) {
+		QListWidgetItem* qItem = new QListWidgetItem;
+		
+		if (entry.bSeparator) {
+			qItem->setText ("--------");
+			qItem->setIcon (getIcon ("empty"));
+		} else {
+			color* col = entry.col;
+			
+			if (col == null) {
+				qItem->setText ("[[unknown color]]");
+				qItem->setIcon (getIcon ("error"));
+			} else {
+				qItem->setText (col->zName);
+				qItem->setIcon (getIcon ("palette"));
+			}
+		}
+		
+		lw_quickColors->addItem (qItem);
+		quickColorItems.push_back (qItem);
+		
+		if (pSel && &entry == pSel) {
+			lw_quickColors->setCurrentItem (qItem);
+			lw_quickColors->scrollToItem (qItem);
+		}
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::slot_setColor () {
+	quickColorMetaEntry* entry = null;
+	QListWidgetItem* item = null;
+	const bool isNew = static_cast<QPushButton*> (sender ()) == pb_addColor;
+	
+	if (isNew == false) {
+		item = getSelectedQuickColor ();
+		if (!item)
+			return;
+		
+		ulong ulIdx = getItemRow (item, quickColorItems);
+		entry = &quickColorMeta[ulIdx];
+		
+		if (entry->bSeparator == true)
+			return; // don't color separators
+	}
+	
+	short dDefault = entry ? entry->col->index () : -1;
+	short dValue;
+	
+	if (ColorSelectDialog::staticDialog (dValue, dDefault, this) == false)
+		return;
+	
+	if (entry)
+		entry->col = getColor (dValue);
+	else {
+		quickColorMetaEntry entry = {getColor (dValue), null, false};
+		
+		item = getSelectedQuickColor ();
+		ulong idx;
+		
+		if (item)
+			idx = getItemRow (item, quickColorItems) + 1;
+		else
+			idx = quickColorItems.size();
+		
+		quickColorMeta.insert (quickColorMeta.begin() + idx, entry);
+		entry = quickColorMeta[idx];
+	}
+	
+	updateQuickColorList (entry);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::slot_delColor () {
+	if (lw_quickColors->selectedItems().size() == 0)
+		return;
+	
+	QListWidgetItem* qItem = lw_quickColors->selectedItems ()[0];
+	ulong ulIdx = getItemRow (qItem, quickColorItems);
+	quickColorMeta.erase (quickColorMeta.begin () + ulIdx);
+	updateQuickColorList ();
+}
+
+// =============================================================================
+void ConfigDialog::slot_moveColor () {
+	const bool bUp = (static_cast<QPushButton*> (sender()) == pb_moveColorUp);
+	
+	if (lw_quickColors->selectedItems().size() == 0)
+		return;
+	
+	QListWidgetItem* qItem = lw_quickColors->selectedItems ()[0];
+	ulong ulIdx = getItemRow (qItem, quickColorItems);
+	
+	long lDest = bUp ? (ulIdx - 1) : (ulIdx + 1);
+	
+	if (lDest < 0 || (ulong)lDest >= quickColorItems.size ())
+		return; // destination out of bounds
+	
+	quickColorMetaEntry tmp = quickColorMeta[lDest];
+	quickColorMeta[lDest] = quickColorMeta[ulIdx];
+	quickColorMeta[ulIdx] = tmp;
+	
+	updateQuickColorList (&quickColorMeta[lDest]);
+}
+
+// =============================================================================
+void ConfigDialog::slot_addColorSeparator() {
+	quickColorMeta.push_back ({null, null, true});
+	updateQuickColorList (&quickColorMeta[quickColorMeta.size () - 1]);
+}
+
+// =============================================================================
+void ConfigDialog::slot_clearColors () {
+	quickColorMeta.clear ();
+	updateQuickColorList ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::makeSlider (QSlider*& slider, short min, short max, short defval) {
+	slider = new QSlider (Qt::Horizontal);
+	slider->setRange (min, max);
+	slider->setSliderPosition (defval);
+	slider->setTickPosition (QSlider::TicksAbove);
+	slider->setTickInterval (1);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+ConfigDialog::~ConfigDialog () {
+	g_ConfigDialog = null;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::pickColor (strconfig& cfg, QPushButton* qButton) {
+	QColorDialog dlg (QColor (cfg.value.chars()));
+	dlg.setWindowIcon (getIcon ("colorselect"));
+	
+	if (dlg.exec ()) {
+		uchar r = dlg.currentColor ().red (),
+			g = dlg.currentColor ().green (),
+			b = dlg.currentColor ().blue ();
+		cfg.value.format ("#%.2X%.2X%.2X", r, g, b);
+		setButtonBackground (qButton, cfg.value);
+	}
+}
+
+void ConfigDialog::slot_setGLBackground () {
+	pickColor (gl_bgcolor, pb_viewBg);
+}
+
+void ConfigDialog::slot_setGLForeground () {
+	pickColor (gl_maincolor, pb_viewFg);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::setButtonBackground (QPushButton* qButton, str zValue) {
+	qButton->setIcon (getIcon ("colorselect"));
+	qButton->setAutoFillBackground (true);
+	qButton->setStyleSheet (
+		fmt ("background-color: %s", zValue.chars()).chars()
+	);
+}
+
+// =============================================================================
+long ConfigDialog::getItemRow (QListWidgetItem* qItem, std::vector<QListWidgetItem*>& haystack) {
+	long i = 0;
+	
+	for (QListWidgetItem* it : haystack) {
+		if (it == qItem)
+			return i;
+		++i;
+	}
+	
+	return -1;
+}
+
+// =============================================================================
+QListWidgetItem* ConfigDialog::getSelectedQuickColor () {
+	if (lw_quickColors->selectedItems().size() == 0)
+		return null;
+	
+	return lw_quickColors->selectedItems ()[0];
+}
+
+// =============================================================================
+QList<ShortcutListItem*> ConfigDialog::getShortcutSelection () {
+	QList<ShortcutListItem*> out;
+	
+	for (QListWidgetItem* entry : lw_shortcutList->selectedItems ())
+		out << static_cast<ShortcutListItem*> (entry);
+	
+	return out;
+}
+
+// =============================================================================
+void ConfigDialog::slot_setShortcut () {
+	QList<ShortcutListItem*> sel = getShortcutSelection ();
+	
+	if (sel.size() < 1)
+		return;
+	
+	ShortcutListItem* item = sel[0];
+	if (KeySequenceDialog::staticDialog (*(item->getActionInfo ()), this))
+		setShortcutText (item, *(item->getActionInfo ()));
+}
+
+// =============================================================================
+void ConfigDialog::slot_resetShortcut () {
+	QList<ShortcutListItem*> sel = getShortcutSelection ();
+	
+	for (ShortcutListItem* item : sel) {
+		actionmeta* info = item->getActionInfo ();
+		keyseqconfig* conf = info->conf;
+		
+		conf->reset ();
+		(*info->qAct)->setShortcut (*conf);
+		
+		setShortcutText (item, *info);
+	}
+}
+
+// =============================================================================
+void ConfigDialog::slot_clearShortcut () {
+	QList<ShortcutListItem*> sel = getShortcutSelection ();
+	QKeySequence dummy;
+	
+	for (ShortcutListItem* item : sel) {
+		actionmeta* info = item->getActionInfo ();
+		keyseqconfig* conf = info->conf;
+		conf->value = dummy;
+		
+		(*info->qAct)->setShortcut (*conf);
+		setShortcutText (item, *info);
+	}
+}
+
+// =============================================================================
+void ConfigDialog::slot_setExtProgPath () {
+	const extProgInfo* info = null;
+	for (const extProgInfo& it : g_extProgInfo) {
+		if (it.setPathButton == sender ()) {
+			info = &it;
+			break;
+		}
+	}
+	
+	assert (info != null);
+	
+	str filter;
+#ifdef _WIN32
+	filter = "Applications (*.exe)(*.exe);;All files (*.*)(*.*)";
+#endif // WIN32
+	
+	str fpath = QFileDialog::getOpenFileName (this, fmt ("Path to %s", info->name), "", filter);
+	if (!~fpath)
+		return;
+	
+	info->input->setText (fpath);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::setShortcutText (QListWidgetItem* qItem, actionmeta meta) {
+	QAction* const act = *meta.qAct;
+	str zLabel = act->iconText ();
+	str zKeybind = act->shortcut ().toString ();
+	
+	qItem->setText (fmt ("%s (%s)", zLabel.chars () ,zKeybind.chars ()).chars());
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+str ConfigDialog::makeColorToolBarString () {
+	str val;
+	
+	for (quickColorMetaEntry entry : quickColorMeta) {
+		if (~val > 0)
+			val += ':';
+		
+		if (entry.bSeparator)
+			val += '|';
+		else
+			val.appendformat ("%d", entry.col->index ());
+	}
+	
+	return val;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ConfigDialog::staticDialog () {
+	ConfigDialog dlg (g_win);
+	
+	if (dlg.exec ()) {
+		lv_colorize = dlg.cb_colorize->isChecked ();
+		gl_colorbfc = dlg.cb_colorBFC->isChecked ();
+		gl_selflash = dlg.cb_selFlash->isChecked ();
+		edit_schemanticinline = dlg.cb_schemanticInline->isChecked ();
+		gl_blackedges = dlg.cb_blackEdges->isChecked ();
+		
+		gl_maincolor_alpha = ((double)dlg.sl_viewFgAlpha->value ()) / 10.0f;
+		gl_linethickness = dlg.sl_lineThickness->value ();
+		gui_toolbar_iconsize = (dlg.sl_iconSize->value () * 4) + 12;
+		
+		// Manage the quick color toolbar
+		g_win->setQuickColorMeta (dlg.quickColorMeta);
+		gui_colortoolbar = dlg.makeColorToolBarString ();
+		
+		// Set the grid settings
+		for (int i = 0; i < g_NumGrids; ++i)
+			for (int j = 0; j < 4; ++j)
+				g_GridInfo[i].confs[j]->value = dlg.dsb_gridData[i][j]->value ();
+		
+		// Ext program settings
+		for (const extProgInfo& info : g_extProgInfo)
+			*info.path = info.input->text ();
+		
+		// Save the config
+		config::save ();
+		
+		// Reload all subfiles as the ldraw path potentially changed.
+		reloadAllSubfiles ();
+		
+		g_win->R ()->setBackground ();
+		g_win->refresh ();
+		g_win->updateToolBars ();
+	}
+}
+
+// =========================================================================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =========================================================================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =========================================================================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =========================================================================================================================
+KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent,
+	Qt::WindowFlags f) : QDialog (parent, f), seq (seq)
+{
+	lb_output = new QLabel;
+	IMPLEMENT_DIALOG_BUTTONS
+	
+	setWhatsThis ("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 (actionmeta& meta, QWidget* parent) {
+	KeySequenceDialog dlg (*meta.conf, parent);
+	
+	if (dlg.exec () == false)
+		return false;
+	
+	*meta.conf = dlg.seq;
+	(*meta.qAct)->setShortcut (*meta.conf);
+	return true;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void KeySequenceDialog::updateOutput () {
+	str zShortcut = seq.toString ();
+	
+	str zText = fmt ("<center><b>%s</b></center>", zShortcut.chars ());
+	
+	lb_output->setText (zText);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) {
+	seq = ev->key ();
+	
+	switch (seq) {
+	case Qt::Key_Shift:
+	case Qt::Key_Control:
+	case Qt::Key_Alt:
+	case Qt::Key_Meta:
+		seq = 0;
+		break;
+	
+	default:
+		break;
+	}
+	
+	seq = (seq | ev->modifiers ());
+	
+	updateOutput ();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/configDialog.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,143 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CONFIGDIALOG_H
+#define CONFIGDIALOG_H
+
+#include "gui.h"
+#include <qdialog.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qdialogbuttonbox.h>
+#include <qpushbutton.h>
+#include <qcheckbox.h>
+#include <qlistwidget.h>
+#include <qspinbox.h>
+
+// =============================================================================
+class ShortcutListItem : public QListWidgetItem {
+public:
+	explicit ShortcutListItem (QListWidget* view = null, int type = Type) :
+		QListWidgetItem (view, type) {}
+	
+	actionmeta* getActionInfo () const { return m_info; }
+	void setActionInfo (actionmeta* info) { m_info = info; }
+	
+private:
+	actionmeta* m_info;
+};
+
+// =============================================================================
+class ConfigDialog : public QDialog {
+	Q_OBJECT
+	
+public:
+	QTabWidget* tabs;
+	QWidget* mainTab, *shortcutsTab, *quickColorTab, *extProgTab;
+	
+	// =========================================================================
+	// Main tab widgets
+	QLabel* lb_viewBg, *lb_viewFg, *lb_viewFgAlpha;
+	QLabel* lb_lineThickness, *lb_iconSize;
+	QPushButton* pb_viewBg, *pb_viewFg;
+	QCheckBox* cb_colorize, *cb_colorBFC, *cb_selFlash, *cb_schemanticInline,
+		*cb_blackEdges;
+	QSlider* sl_viewFgAlpha, *sl_lineThickness, *sl_iconSize;
+	
+	// =========================================================================
+	// Shortcuts tab
+	QListWidget* lw_shortcutList;
+	QPushButton* pb_setShortcut, *pb_resetShortcut, *pb_clearShortcut;
+	
+	// =========================================================================
+	// Quick color toolbar tab
+	QListWidget* lw_quickColors;
+	QPushButton* pb_addColor, *pb_delColor, *pb_changeColor, *pb_addColorSeparator,
+		*pb_moveColorUp, *pb_moveColorDown, *pb_clearColors;
+	std::vector<QListWidgetItem*> quickColorItems;
+	std::vector<quickColorMetaEntry> quickColorMeta;
+	
+	// =========================================================================
+	// Grid tab
+	QLabel* lb_gridLabels[3];
+	QLabel* lb_gridIcons[3];
+	QDoubleSpinBox* dsb_gridData[3][4];
+	
+	// =========================================================================
+	QDialogButtonBox* bbx_buttons;
+	
+	ConfigDialog (ForgeWindow* parent);
+	~ConfigDialog ();
+	static void staticDialog ();
+	
+private:
+	void initMainTab ();
+	void initShortcutsTab ();
+	void initQuickColorTab ();
+	void initGridTab ();
+	void initExtProgTab ();
+	
+	void makeSlider (QSlider*& slider, short min, short max, short defval);
+	void setButtonBackground (QPushButton* qButton, str zValue);
+	void pickColor (strconfig& cfg, QPushButton* qButton);
+	void updateQuickColorList (quickColorMetaEntry* pSel = null);
+	void setShortcutText (QListWidgetItem* qItem, actionmeta meta);
+	long getItemRow (QListWidgetItem* qItem, std::vector<QListWidgetItem*>& haystack);
+	str makeColorToolBarString ();
+	QListWidgetItem* getSelectedQuickColor ();
+	QList<ShortcutListItem*> getShortcutSelection ();
+	
+private slots:
+	void slot_setGLBackground ();
+	void slot_setGLForeground ();
+	
+	void slot_setShortcut ();
+	void slot_resetShortcut ();
+	void slot_clearShortcut ();
+	
+	void slot_setColor ();
+	void slot_delColor ();
+	void slot_addColorSeparator ();
+	void slot_moveColor ();
+	void slot_clearColors ();
+	
+	void slot_setExtProgPath ();
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class KeySequenceDialog : public QDialog {
+	Q_OBJECT
+	
+public:
+	explicit KeySequenceDialog (QKeySequence seq, QWidget* parent = null, Qt::WindowFlags f = 0);
+	static bool staticDialog (actionmeta& meta, QWidget* parent = null);
+	
+	QLabel* lb_output;
+	QDialogButtonBox* bbx_buttons;
+	QKeySequence seq;
+	
+private:
+	void updateOutput ();
+	
+private slots:
+	virtual void keyPressEvent (QKeyEvent* ev);
+};
+
+#endif // CONFIGDIALOG_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/extprogs.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,461 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <qprocess.h>
+#include <qtemporaryfile.h>
+#include <qeventloop.h>
+#include <qdialog.h>
+#include <qdialogbuttonbox.h>
+#include <qspinbox.h>
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include "common.h"
+#include "config.h"
+#include "misc.h"
+#include "extprogs.h"
+#include "gui.h"
+#include "file.h"
+#include "radiobox.h"
+#include "history.h"
+
+// =============================================================================
+cfg (str, prog_isecalc, "");
+cfg (str, prog_intersector, "");
+cfg (str, prog_coverer, "");
+cfg (str, prog_ytruder, "");
+cfg (str, prog_datheader, "");
+cfg (str, prog_rectifier, "");
+
+const char* g_extProgNames[] = {
+	"Isecalc",
+	"Intersector",
+	"Coverer",
+	"Ytruder",
+	"Rectifier",
+	"DATHeader",
+};
+
+// =============================================================================
+static bool checkProgPath (str path, const extprog prog) {
+	if (~path)
+		return true;
+	
+	const char* name = g_extProgNames[prog];
+	
+	critical (fmt ("Couldn't run %s as no path has "
+		"been defined for it. Use the configuration dialog's External Programs "
+		"tab to define a path for %s.", name, name));
+	return false;
+}
+
+// =============================================================================
+static void processError (const extprog prog, QProcess& proc) {
+	const char* name = g_extProgNames[prog];
+	str errmsg;
+	
+	switch (proc.error ()) {
+	case QProcess::FailedToStart:
+		errmsg = fmt ("Failed to launch %s. Check that you have set the proper path "
+			"to %s and that you have the proper permissions to launch it.", name, name);
+		break;
+	
+	case QProcess::Crashed:
+		errmsg = fmt ("%s crashed.", name);
+		break;
+	
+	case QProcess::WriteError:
+	case QProcess::ReadError:
+		errmsg = fmt ("I/O error while interacting with %s.", name);
+		break;
+	
+	case QProcess::UnknownError:
+		errmsg = fmt ("Unknown error occurred while executing %s.", name);
+		break;
+	
+	case QProcess::Timedout:
+		errmsg = fmt ("%s timed out.", name);
+		break;
+	}
+	
+	critical (errmsg);
+}
+
+// =============================================================================
+static bool mkTempFile (QTemporaryFile& tmp, str& fname) {
+	if (!tmp.open ())
+		return false;
+	
+	fname = tmp.fileName ();
+	tmp.close ();
+	return true;
+}
+
+// =============================================================================
+void writeObjects (std::vector<LDObject*>& objects, str fname) {
+	// Write the input file
+	FILE* fp = fopen (fname, "w");
+	if (!fp) {
+		critical (fmt ("Couldn't open temporary file %s for writing.\n", fname.chars ()));
+		return;
+	}
+	
+	for (LDObject* obj : objects) {
+		str line = fmt ("%s\r\n", obj->getContents ().chars ());
+		fwrite (line.chars(), 1, ~line, fp);
+	}
+	
+#ifndef RELEASE
+	ushort idx = rand ();
+	printf ("%s -> debug_%u\n", fname.chars (), idx);
+	QFile::copy (fname.chars (), fmt ("debug_%u", idx));
+#endif // RELEASE
+	
+	fclose (fp);
+}
+
+// =============================================================================
+void writeSelection (str fname) {
+	writeObjects (g_win->sel (), fname);
+}
+
+// =============================================================================
+void writeColorGroup (const short colnum, str fname) {
+	std::vector<LDObject*> objects;
+	for (LDObject*& obj : g_curfile->m_objs) {
+		if (obj->isColored () == false || obj->dColor != colnum)
+			continue;
+		
+		objects.push_back (obj);
+	}
+	
+	writeObjects (objects, fname);
+}
+
+// =============================================================================
+void runUtilityProcess (extprog prog, str path, QString argvstr) {
+	QTemporaryFile input, output;
+	str inputname, outputname;
+	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
+	
+	printf ("cmdline: %s %s\n", path.chars (), qchars (argvstr));
+	
+	if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname))
+		return;
+	
+	QProcess proc;
+	
+	// Init stdin
+	FILE* stdinfp = fopen (inputname, "w");
+	
+	// Begin!
+	proc.setStandardInputFile (inputname);
+	proc.start (path, argv);
+	
+	// Write an enter - one is expected
+	char enter[2] = "\n";
+	enter[1] = '\0';
+	fwrite (enter, 1, sizeof enter, stdinfp);
+	fflush (stdinfp);
+	
+	// Wait while it runs
+	proc.waitForFinished ();
+	
+#ifndef RELASE
+	printf ("%s", qchars (QString (proc.readAllStandardOutput ())));
+#endif // RELEASE
+	
+	if (proc.exitStatus () == QProcess::CrashExit) {
+		processError (prog, proc);
+		return;
+	}
+}
+
+// ========================================================================================================================================
+static void insertOutput (str fname, bool replace, vector<short> colorsToReplace) {
+#ifndef RELEASE
+	QFile::copy (fname, "./debug_lastOutput");
+#endif // RELEASE
+	
+	// Read the output file
+	FILE* fp = fopen (fname, "r");
+	if (!fp) {
+		critical (fmt ("Couldn't open temporary file %s for reading.\n", fname.chars ()));
+		return;
+	}
+	
+	ComboHistory* cmb = new ComboHistory ({});
+	std::vector<LDObject*> objs = loadFileContents (fp, null),
+		copies;
+	std::vector<ulong> indices;
+	
+	// If we replace the objects, delete the selection now.
+	if (replace)
+		*cmb << g_win->deleteSelection ();
+	
+	for (const short colnum : colorsToReplace)
+		*cmb << g_win->deleteByColor (colnum);
+	
+	// Insert the new objects
+	g_win->sel ().clear ();
+	for (LDObject* obj : objs) {
+		if (!obj->isSchemantic ()) {
+			delete obj;
+			continue;
+		}
+		
+		ulong idx = g_curfile->addObject (obj);
+		indices.push_back (idx);
+		copies.push_back (obj->clone ());
+		g_win->sel ().push_back (obj);
+	}
+	
+	if (indices.size() > 0)
+		*cmb << new AddHistory ({indices, copies});
+	
+	if (cmb->paEntries.size () > 0)
+		History::addEntry (cmb);
+	else
+		delete cmb;
+	
+	fclose (fp);
+	g_win->refresh ();
+}
+
+QDialogButtonBox* makeButtonBox (QDialog& dlg) {
+	QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+	QWidget::connect (bbx_buttons, SIGNAL (accepted ()), &dlg, SLOT (accept ()));
+	QWidget::connect (bbx_buttons, SIGNAL (rejected ()), &dlg, SLOT (reject ()));
+	return bbx_buttons;
+}
+
+// =============================================================================
+// Interface for Ytruder
+MAKE_ACTION (ytruder, "Ytruder", "ytruder", "Extrude selected lines to a given plane", KEY (F4)) {
+	setlocale (LC_ALL, "C");
+	
+	if (!checkProgPath (prog_ytruder, Ytruder))
+		return;
+	
+	QDialog dlg;
+	
+	RadioBox* rb_mode = new RadioBox ("Extrusion mode", {"Distance", "Symmetry", "Projection", "Radial"}, 0, Qt::Horizontal);
+	RadioBox* rb_axis = new RadioBox ("Axis", {"X", "Y", "Z"}, 0, Qt::Horizontal);
+	LabeledWidget<QDoubleSpinBox>* dsb_depth = new LabeledWidget<QDoubleSpinBox> ("Plane depth"),
+		*dsb_condAngle = new LabeledWidget<QDoubleSpinBox> ("Conditional line threshold");
+	
+	rb_axis->setValue (Y);
+	dsb_depth->w ()->setMinimum (-10000.0);
+	dsb_depth->w ()->setMaximum (10000.0);
+	dsb_depth->w ()->setDecimals (3);
+	dsb_condAngle->w ()->setValue (30.0f);
+	
+	QVBoxLayout* layout = new QVBoxLayout (&dlg);
+	layout->addWidget (rb_mode);
+	layout->addWidget (rb_axis);
+	layout->addWidget (dsb_depth);
+	layout->addWidget (dsb_condAngle);
+	layout->addWidget (makeButtonBox (dlg));
+	
+	dlg.setWindowIcon (getIcon ("extrude"));
+	
+	if (!dlg.exec ())
+		return;
+	
+	// Read the user's choices
+	const enum modetype { Distance, Symmetry, Projection, Radial } mode = (modetype) rb_mode->value ();
+	const Axis axis = (Axis) rb_axis->value ();
+	const double depth = dsb_depth->w ()->value (),
+		condAngle = dsb_condAngle->w ()->value ();
+	
+	QTemporaryFile indat, outdat;
+	str inDATName, outDATName;
+	
+	// Make temp files for the input and output files
+	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
+		return;
+	
+	// Compose the command-line arguments
+	str argv = fmt ("%s %s %f -a %f %s %s",
+		(axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
+		(mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
+		depth, condAngle, inDATName.chars (), outDATName.chars ());
+	
+	writeSelection (inDATName);
+	runUtilityProcess (Ytruder, prog_ytruder, argv);
+	insertOutput (outDATName, false, {});
+}
+
+// ========================================================================================================================================
+// Rectifier interface
+MAKE_ACTION (rectifier, "Rectifier", "rectifier", "Optimizes quads into rect primitives.", KEY (F8)) {
+	setlocale (LC_ALL, "C");
+	
+	if (!checkProgPath (prog_rectifier, Rectifier))
+		return;
+	
+	QDialog dlg;
+	QCheckBox* cb_condense = new QCheckBox ("Condense triangles to quads"),
+		*cb_subst = new QCheckBox ("Substitute rect primitives"),
+		*cb_condlineCheck = new QCheckBox ("Don't replace quads with adj. condlines"),
+		*cb_colorize = new QCheckBox ("Colorize resulting objects");
+	LabeledWidget<QDoubleSpinBox>* dsb_coplthres = new LabeledWidget<QDoubleSpinBox> ("Coplanarity threshold");
+	
+	dsb_coplthres->w ()->setMinimum (0.0f);
+	dsb_coplthres->w ()->setMaximum (360.0f);
+	dsb_coplthres->w ()->setDecimals (3);
+	dsb_coplthres->w ()->setValue (0.95f);
+	cb_condense->setChecked (true);
+	cb_subst->setChecked (true);
+	
+	QVBoxLayout* layout = new QVBoxLayout (&dlg);
+	layout->addWidget (cb_condense);
+	layout->addWidget (cb_subst);
+	layout->addWidget (cb_condlineCheck);
+	layout->addWidget (cb_colorize);
+	layout->addWidget (dsb_coplthres);
+	layout->addWidget (makeButtonBox (dlg));
+	
+	if (!dlg.exec ())
+		return;
+	
+	const bool condense = cb_condense->isChecked (),
+		subst = cb_subst->isChecked (),
+		condlineCheck = cb_condlineCheck->isChecked (),
+		colorize = cb_colorize->isChecked ();
+	const double coplthres = dsb_coplthres->w ()->value ();
+	
+	QTemporaryFile indat, outdat;
+	str inDATName, outDATName;
+	
+	// Make temp files for the input and output files
+	if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
+		return;
+	
+	// Compose arguments
+	str argv = fmt ("%s %s %s %s -t %f %s %s",
+		(condense == false) ? "-q" : "",
+		(subst == false) ? "-r" : "",
+		(condlineCheck) ? "-a" : "",
+		(colorize) ? "-c" : "",
+		coplthres, inDATName.chars (), outDATName.chars ());
+	
+	writeSelection (inDATName);
+	runUtilityProcess (Rectifier, prog_rectifier, argv);
+	insertOutput (outDATName, true, {});
+}
+
+// =======================================================================================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =======================================================================================================================================
+// Intersector interface
+MAKE_ACTION (intersector, "Intersector", "intersector", "Perform clipping between two input groups.", KEY (F5)) {
+	setlocale (LC_ALL, "C");
+	
+	if (!checkProgPath (prog_intersector, Intersector))
+		return;
+	
+	QDialog dlg;
+	
+	LabeledWidget<QComboBox>* cmb_incol = new LabeledWidget<QComboBox> ("Input", new QComboBox),
+		*cmb_cutcol = new LabeledWidget<QComboBox> ("Cutter", new QComboBox);
+	QCheckBox* cb_colorize = new QCheckBox ("Colorize output"),
+		*cb_nocondense = new QCheckBox ("No condensing"),
+		*cb_repeatInverse = new QCheckBox ("Repeat inverse"),
+		*cb_edges = new QCheckBox ("Add edges");
+	LabeledWidget<QDoubleSpinBox>* dsb_prescale = new LabeledWidget<QDoubleSpinBox> ("Prescaling factor");
+	
+	cb_repeatInverse->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.");
+	cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection.");
+	
+	makeColorSelector (cmb_incol->w ());
+	makeColorSelector (cmb_cutcol->w ());
+	dsb_prescale->w ()->setMinimum (0.0f);
+	dsb_prescale->w ()->setMaximum (10000.0f);
+	dsb_prescale->w ()->setSingleStep (0.01f);
+	dsb_prescale->w ()->setValue (1.0f);
+	
+	QVBoxLayout* layout = new QVBoxLayout (&dlg);
+	layout->addWidget (cmb_incol);
+	layout->addWidget (cmb_cutcol);
+	
+	QHBoxLayout* cblayout = new QHBoxLayout;
+	cblayout->addWidget (cb_colorize);
+	cblayout->addWidget (cb_nocondense);
+	
+	QHBoxLayout* cb2layout = new QHBoxLayout;
+	cb2layout->addWidget (cb_repeatInverse);
+	cb2layout->addWidget (cb_edges);
+	
+	layout->addLayout (cblayout);
+	layout->addLayout (cb2layout);
+	layout->addWidget (dsb_prescale);
+	layout->addWidget (makeButtonBox (dlg));
+	
+exec:
+	if (!dlg.exec ())
+		return;
+	
+	const short inCol = cmb_incol->w ()->itemData (cmb_incol->w ()->currentIndex ()).toInt (),
+		cutCol =  cmb_cutcol->w ()->itemData (cmb_cutcol->w ()->currentIndex ()).toInt ();
+	const bool repeatInverse = cb_repeatInverse->isChecked ();
+	
+	if (inCol == cutCol) {
+		critical ("Cannot use the same color group for both input and cutter!");
+		goto exec;
+	}
+	
+	// Five temporary files!
+	// indat = input group file
+	// cutdat = cutter group file
+	// outdat = primary output
+	// outdat2 = inverse output
+	// edgesdat = edges output (isecalc)
+	QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat;
+	str inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
+	
+	if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) ||
+		!mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) ||
+		!mkTempFile (edgesdat, edgesDATName))
+	{
+		return;
+	}
+	
+	str parms = fmt ("%s %s -s %f",
+		(cb_colorize->isChecked ()) ? "-c" : "",
+		(cb_nocondense->isChecked ()) ? "-t" : "",
+		dsb_prescale->w ()->value ());
+	
+	str argv_normal = fmt ("%s %s %s %s", parms.chars (), inDATName.chars (), cutDATName.chars (), outDATName.chars ());
+	str argv_inverse = fmt ("%s %s %s %s", parms.chars (), cutDATName.chars (), inDATName.chars (), outDAT2Name.chars ());
+	
+	writeColorGroup (inCol, inDATName);
+	writeColorGroup (cutCol, cutDATName);
+	runUtilityProcess (Intersector, prog_intersector, argv_normal);
+	insertOutput (outDATName, false, {inCol});
+	
+	if (repeatInverse) {
+		runUtilityProcess (Intersector, prog_intersector, argv_inverse);
+		insertOutput (outDAT2Name, false, {cutCol});
+	}
+	
+	if (cb_edges->isChecked ()) {
+		runUtilityProcess (Isecalc, prog_isecalc, fmt ("%s %s %s", inDATName.chars (), cutDATName.chars (), edgesDATName.chars ()));
+		insertOutput (edgesDATName, false, {});
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/extprogs.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,33 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EXTPROGS_H
+#define EXTPROGS_H
+
+#include <qobject.h>
+
+enum extprog {
+	Isecalc,
+	Intersector,
+	Coverer,
+	Ytruder,
+	Rectifier,
+	DATHeader
+};
+
+#endif // EXTPROGS_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/file.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,757 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <vector>
+#include <stdio.h>
+#include <qmessagebox.h>
+#include <QDir>
+#include "common.h"
+#include "config.h"
+#include "file.h"
+#include "misc.h"
+#include "bbox.h"
+#include "gui.h"
+#include "history.h"
+#include "ldrawPathDialog.h"
+
+cfg (str, io_ldpath, "");
+cfg (str, io_recentfiles, "");
+
+// =============================================================================
+namespace LDPaths {
+	static str pathError;
+	
+	struct {
+		str LDConfigPath;
+		str partsPath, primsPath;
+	} pathInfo;
+	
+	void initPaths () {
+		if (!tryConfigure (io_ldpath)) {
+			LDrawPathDialog dlg (false);
+			
+			if (!dlg.exec ())
+				exit (0);
+			
+			io_ldpath = dlg.path ();
+		}
+	}
+	
+	bool tryConfigure (str path) {
+		QDir dir;
+		
+		if (!dir.cd (path)) {
+			pathError = "Directory does not exist.";
+			return false;
+		}
+		
+		QStringList mustHave = {"LDConfig.ldr", "parts", "p"};
+		QStringList contents = dir.entryList (mustHave);
+		
+		if (contents.size () != mustHave.size ()) {
+			pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/.";
+			return false;
+		}
+		
+		pathInfo.partsPath = fmt ("%s" DIRSLASH "parts", path.chars ());
+		pathInfo.LDConfigPath = fmt ("%s" DIRSLASH "LDConfig.ldr", path.chars ());
+		pathInfo.primsPath = fmt ("%s" DIRSLASH "p", path.chars ());
+		
+		return true;
+	}
+	
+	// Accessors
+	str getError () { return pathError; }
+	str ldconfig () { return pathInfo.LDConfigPath; }
+	str prims () { return pathInfo.primsPath; }
+	str parts () { return pathInfo.partsPath; }
+}
+
+// =============================================================================
+OpenFile::OpenFile () {
+	m_implicit = true;
+	savePos = -1;
+}
+
+// =============================================================================
+OpenFile::~OpenFile () {
+	// Clear everything from the model
+	for (LDObject* obj : m_objs)
+		delete obj;
+	
+	// Clear the cache as well
+	for (LDObject* obj : m_objCache)
+		delete obj;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+OpenFile* findLoadedFile (str zName) {
+	for (OpenFile* file : g_loadedFiles)
+		if (file->m_filename == zName)
+			return file;
+	
+	return null;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+FILE* openLDrawFile (str path, bool bSubDirectories) {
+	str zTruePath = path;
+	
+#ifndef WIN32
+	zTruePath.replace ("\\", "/");
+#endif // WIN32
+	
+	FILE* fp = fopen (path.chars (), "r");
+	str zFilePath;
+	
+	if (fp != null)
+		return fp;
+	
+	if (~io_ldpath.value) {
+		// Try with just the LDraw path first
+		zFilePath = fmt ("%s" DIRSLASH "%s",
+			io_ldpath.value.chars(), zTruePath.chars());
+		logf ("Trying %s\n", zFilePath.chars());
+		
+		fp = fopen (zFilePath, "r");
+		if (fp != null)
+			return fp;
+		
+		if (bSubDirectories) {
+			char const* saSubdirectories[] = {
+				"parts",
+				"p",
+			};
+			
+			for (char const* sSubdir : saSubdirectories) {
+				zFilePath = fmt ("%s" DIRSLASH "%s" DIRSLASH "%s",
+					io_ldpath.value.chars(), sSubdir, zTruePath.chars());
+				printf ("try %s\n", zFilePath.chars());
+				
+				fp = fopen (zFilePath.chars (), "r");
+				
+				if (fp)
+					return fp;
+			}
+		}
+	}
+	
+	return null;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+std::vector<LDObject*> loadFileContents (FILE* fp, ulong* numWarnings) {
+	char line[1024];
+	vector<str> lines;
+	vector<LDObject*> objs;
+	ulong lnum = 0;
+	
+	if (numWarnings)
+		*numWarnings = 0;
+	
+	while (fgets (line, sizeof line, fp)) {
+		// Trim the trailing newline
+		str data = line;
+		while (data[~data - 1] == '\n' || data[~data - 1] == '\r')
+			data -= 1;
+		
+		LDObject* obj = parseLine (data);
+		assert (obj != null);
+		
+		// Check for parse errors and warn about tthem
+		if (obj->getType() == LDObject::Gibberish) {
+			logf (LOG_Warning, "Couldn't parse line #%lu: %s\n",
+				lnum, static_cast<LDGibberish*> (obj)->zReason.chars());
+			
+			logf (LOG_Warning, "- Line was: %s\n", data.chars());
+			
+			if (numWarnings)
+				(*numWarnings)++;
+		}
+		
+		objs.push_back (obj);
+		lnum++;
+	}
+	
+	return objs;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+OpenFile* openDATFile (str path, bool search) {
+	logf ("Opening %s...\n", path.chars());
+	
+	// Convert the file name to lowercase since some parts contain uppercase
+	// file names. I'll assume here that the library will always use lowercase
+	// file names for the actual parts..
+	FILE* fp;
+	if (search)
+		fp = openLDrawFile (-path, true);
+	else
+		fp = fopen (path, "r");
+	
+	if (!fp) {
+		logf (LOG_Error, "Couldn't open %s: %s\n", path.chars (), strerror (errno));
+		return null;
+	}
+	
+	OpenFile* load = new OpenFile;
+	load->m_filename = path;
+	ulong numWarnings;
+	std::vector<LDObject*> objs = loadFileContents (fp, &numWarnings);
+	
+	for (LDObject* obj : objs)
+		load->m_objs.push_back (obj);
+	
+	fclose (fp);
+	g_loadedFiles.push_back (load);
+	
+	logf ("File %s parsed successfully (%lu warning%s).\n",
+		path.chars(), numWarnings, PLURAL (numWarnings));
+	
+	return load;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+bool OpenFile::safeToClose () {
+	setlocale (LC_ALL, "C");
+	
+	// If we have unsaved changes, warn and give the option of saving.
+	if (!m_implicit && History::pos () != savePos) {
+		switch (QMessageBox::question (g_win, "Unsaved Changes",
+			fmt ("There are unsaved changes to %s. Should it be saved?", m_filename.chars ()),
+			(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel), QMessageBox::Cancel))
+		{
+		case QMessageBox::Yes:
+			if (!save ()) {
+				str errormsg = fmt ("Failed to save %s: %s\nDo you still want to close?",
+					m_filename.chars (), strerror (lastError));
+				
+				if (QMessageBox::critical (g_win, "Save Failure", errormsg,
+					(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::No)
+				{
+					return false;
+				}
+			}
+			
+			break;
+		
+		case QMessageBox::Cancel:
+			return false;
+		
+		default:
+			break;
+		}
+	}
+	
+	return true;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void closeAll () {
+	if (!g_loadedFiles.size())
+		return;
+	
+	// Remove all loaded files and the objects they contain
+	for (OpenFile* file : g_loadedFiles)
+		delete file;
+	
+	// Clear the array
+	g_loadedFiles.clear();
+	g_curfile = NULL;
+	
+	g_win->refresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void newFile () {
+	// Create a new anonymous file and set it to our current
+	closeAll ();
+	
+	OpenFile* f = new OpenFile;
+	f->m_filename = "";
+	g_loadedFiles.push_back (f);
+	g_curfile = f;
+	
+	g_BBox.calculate();
+	g_win->refresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void addRecentFile (str zPath) {
+	long lPos = io_recentfiles.value.first (zPath);
+	
+	// If this file already is in the list, pop it out.
+	if (lPos != -1) {
+		if (~io_recentfiles.value == ~zPath)
+			return; // only recent file - do nothing
+		
+		// Pop it out.
+		str zFront = io_recentfiles.value.substr (0, lPos);
+		str zBack = io_recentfiles.value.substr (lPos + ~zPath + 1, -1);
+		io_recentfiles.value = zFront + zBack;
+	}
+	
+	// If there's too many recent files, drop one out.
+	while (io_recentfiles.value.count ('@') > 3)
+		io_recentfiles.value = io_recentfiles.value.substr (io_recentfiles.value.first ("@") + 1, -1);
+	
+	// Add the file
+	if (~io_recentfiles.value > 0)
+		io_recentfiles.value += "@";
+	
+	io_recentfiles += zPath;
+	
+	config::save ();
+	g_win->updateRecentFilesMenu ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void openMainFile (str path) {
+	closeAll ();
+	
+	OpenFile* file = openDATFile (path, false);
+	
+	if (!file) {
+		// Tell the user loading failed.
+		setlocale (LC_ALL, "C");
+		critical (fmt ("Failed to open %s: %s", path.chars(), strerror (errno)));
+		return;
+	}
+	
+	file->m_implicit = false;
+	g_curfile = file;
+	
+	// Recalculate the bounding box
+	g_BBox.calculate();
+	
+	// Rebuild the object tree view now.
+	g_win->refresh ();
+	g_win->setTitle ();
+	g_win->R ()->resetAngles ();
+	
+	History::clear ();
+	
+	// Add it to the recent files list.
+	addRecentFile (path);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+bool OpenFile::save (str path) {
+	if (!~path)
+		path = m_filename;
+	
+	FILE* fp = fopen (path, "w");
+	
+	if (!fp) {
+		lastError = errno;
+		return false;
+	}
+	
+	// Write all entries now
+	for (LDObject* obj : m_objs) {
+		// LDraw requires lines to have DOS line endings
+		str zLine = fmt ("%s\r\n", obj->getContents ().chars ());
+		
+		fwrite (zLine.chars(), 1, ~zLine, fp);
+	}
+	
+	fclose (fp);
+	
+	// We have successfully saved, update the save position now.
+	savePos = History::pos ();
+	
+	g_win->setTitle ();
+	
+	return true;
+}
+
+#define CHECK_TOKEN_COUNT(N) \
+	if (tokens.size() != N) \
+		return new LDGibberish (zLine, "Bad amount of tokens");
+
+#define CHECK_TOKEN_NUMBERS(MIN,MAX) \
+	for (ushort i = MIN; i <= MAX; ++i) \
+		if (!isNumber (tokens[i])) \
+			return new LDGibberish (zLine, fmt ("Token #%u was `%s`, expected a number", \
+				(i + 1), tokens[i].chars()));
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static vertex parseVertex (vector<str>& s, const ushort n) {
+	// Disable the locale while parsing the line or atof's behavior changes
+	// between locales (i.e. fails to read decimals properly). That is
+	// quite undesired...
+	setlocale (LC_NUMERIC, "C");
+	
+	vertex v;
+	for (const Axis ax : g_Axes)
+		v[ax] = atof (s[n + ax]);
+	
+	return v;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+LDObject* parseLine (str zLine) {
+	vector<str> tokens = zLine.split (" ", true);
+	
+	if (!tokens.size ()) {
+		// Line was empty, or only consisted of whitespace
+		return new LDEmpty;
+	}
+	
+	if (~tokens[0] != 1)
+		return new LDGibberish (zLine, "Illogical line code");
+	
+	const char c = tokens[0][0];
+	switch (c - '0') {
+	case 0:
+		{
+			// Comment
+			str comm;
+			for (uint i = 1; i < tokens.size(); ++i) {
+				comm += tokens[i];
+				
+				if (i != tokens.size() - 1)
+					comm += ' ';
+			}
+			
+			// Handle BFC statements
+			if (tokens.size() > 2 && tokens[1] == "BFC") {
+				for (short i = 0; i < LDBFC::NumStatements; ++i)
+					if (comm == fmt ("BFC %s", LDBFC::statements [i]))
+						return new LDBFC ((LDBFC::Type) i);
+				
+				// MLCAD is notorious for stuffing these statements in parts it
+				// creates. The above block only handles valid statements, so we
+				// need to handle MLCAD-style invertnext separately.
+				if (comm == "BFC CERTIFY INVERTNEXT")
+					return new LDBFC (LDBFC::InvertNext);
+			}
+			
+			if (tokens.size() > 2 && tokens[1] == "!LDFORGE") {
+				// Handle LDForge-specific types, they're embedded into comments
+				
+				if (tokens[2] == "VERTEX") {
+					// Vertex (0 !LDFORGE VERTEX)
+					CHECK_TOKEN_COUNT (7)
+					CHECK_TOKEN_NUMBERS (3, 6)
+					
+					LDVertex* obj = new LDVertex;
+					obj->dColor = atol (tokens[3]);
+					
+					for (const Axis ax : g_Axes)
+						obj->vPosition[ax] = atof (tokens[4 + ax]); // 4 - 6
+					
+					return obj;
+				}
+				
+				if (tokens[2] == "RADIAL") {
+					CHECK_TOKEN_COUNT (20)
+					CHECK_TOKEN_NUMBERS (4, 19)
+					
+					LDRadial::Type eType = LDRadial::NumTypes;
+					
+					for (int i = 0; i < LDRadial::NumTypes; ++i) {
+						if (str (LDRadial::radialTypeName ((LDRadial::Type) i)).toupper ().strip (' ') == tokens[3]) {
+							eType = (LDRadial::Type) i;
+							break;
+						}
+					}
+					
+					if (eType == LDRadial::NumTypes)
+						return new LDGibberish (zLine, fmt ("Unknown radial type %s", tokens[3].chars ()));
+					
+					LDRadial* obj = new LDRadial;
+					
+					obj->eRadialType = eType;			// 3
+					obj->dColor = atol (tokens[4]);		// 4
+					obj->dSegments = atol (tokens[5]);	// 5
+					obj->dDivisions = atol (tokens[6]);	// 6
+					obj->dRingNum = atol (tokens[7]);	// 7
+					
+					obj->vPosition = parseVertex (tokens, 8); // 8 - 10
+					
+					for (short i = 0; i < 9; ++i)
+						obj->mMatrix[i] = atof (tokens[i + 11]); // 11 - 19
+					
+					return obj;
+				}
+			}
+			
+			LDComment* obj = new LDComment;
+			obj->text = comm;
+			return obj;
+		}
+	
+	case 1:
+		{
+			// Subfile
+			CHECK_TOKEN_COUNT (15)
+			CHECK_TOKEN_NUMBERS (1, 13)
+			
+			// Try open the file
+			OpenFile* pFile = loadSubfile (tokens[14]);
+			
+			// If we cannot open the file, mark it an error
+			if (!pFile)
+				return new LDGibberish (zLine, "Could not open referred file");
+			
+			LDSubfile* obj = new LDSubfile;
+			obj->dColor = atol (tokens[1]);
+			obj->vPosition = parseVertex (tokens, 2); // 2 - 4
+			
+			for (short i = 0; i < 9; ++i)
+				obj->mMatrix[i] = atof (tokens[i + 5]); // 5 - 13
+			
+			obj->zFileName = tokens[14];
+			obj->pFile = pFile;
+			return obj;
+		}
+	
+	case 2:
+		{
+			CHECK_TOKEN_COUNT (8)
+			CHECK_TOKEN_NUMBERS (1, 7)
+			
+			// Line
+			LDLine* obj = new LDLine;
+			obj->dColor = atol (tokens[1]);
+			for (short i = 0; i < 2; ++i)
+				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 7
+			return obj;
+		}
+	
+	case 3:
+		{
+			CHECK_TOKEN_COUNT (11)
+			CHECK_TOKEN_NUMBERS (1, 10)
+			
+			// Triangle
+			LDTriangle* obj = new LDTriangle;
+			obj->dColor = atol (tokens[1]);
+			
+			for (short i = 0; i < 3; ++i)
+				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 10
+			
+			return obj;
+		}
+	
+	case 4:
+		{
+			CHECK_TOKEN_COUNT (14)
+			CHECK_TOKEN_NUMBERS (1, 13)
+			
+			// Quadrilateral
+			LDQuad* obj = new LDQuad;
+			obj->dColor = atol (tokens[1]);
+			
+			for (short i = 0; i < 4; ++i)
+				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 13
+			
+			return obj;
+		}
+	
+	case 5:
+		{
+			CHECK_TOKEN_COUNT (14)
+			CHECK_TOKEN_NUMBERS (1, 13)
+			
+			// Conditional line
+			LDCondLine* obj = new LDCondLine;
+			obj->dColor = atol (tokens[1]);
+			
+			for (short i = 0; i < 4; ++i)
+				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 13
+			
+			return obj;
+		}
+	
+	default: // Strange line we couldn't parse
+		return new LDGibberish (zLine, "Unknown line code number");
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+OpenFile* loadSubfile (str zFile) {
+	// Try open the file
+	OpenFile* pFile = findLoadedFile (zFile);
+	if (!pFile)
+		pFile = openDATFile (zFile, true);
+	
+	return pFile;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void reloadAllSubfiles () {
+	if (!g_curfile)
+		return;
+	
+	// First, close all but the current open file.
+	for (OpenFile* file : g_loadedFiles)
+		if (file != g_curfile)
+			delete file;
+	
+	g_loadedFiles.clear ();
+	g_loadedFiles.push_back (g_curfile);
+	
+	// Go through all objects in the current file and reload the subfiles
+	for (LDObject* obj : g_curfile->m_objs) {
+		if (obj->getType() == LDObject::Subfile) {
+			// Note: ref->pFile is invalid right now since all subfiles were closed.
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			OpenFile* pFile = loadSubfile (ref->zFileName);
+			
+			if (pFile)
+				ref->pFile = pFile;
+			else {
+				// Couldn't load the file, mark it an error
+				ref->replace (new LDGibberish (ref->getContents (), "Could not open referred file"));
+			}
+		}
+		
+		// Reparse gibberish files. It could be that they are invalid because
+		// the file could not be opened. Circumstances may be different now.
+		if (obj->getType() == LDObject::Gibberish)
+			obj->replace (parseLine (static_cast<LDGibberish*> (obj)->zContents));
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+ulong OpenFile::addObject (LDObject* obj) {
+	m_objs.push_back (obj);
+	
+	if (this == g_curfile)
+		g_BBox.calcObject (obj);
+	
+	return m_objs.size() - 1;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void OpenFile::insertObj (const ulong pos, LDObject* obj) {
+	m_objs.insert (m_objs.begin () + pos, obj);
+	
+	if (this == g_curfile)
+		g_BBox.calcObject (obj);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void OpenFile::forgetObject (LDObject* obj) {
+	// Find the index for the given object
+	ulong ulIndex;
+	for (ulIndex = 0; ulIndex < (ulong)m_objs.size(); ++ulIndex)
+		if (m_objs[ulIndex] == obj)
+			break; // found it
+	
+	if (ulIndex >= m_objs.size ())
+		return; // was not found
+	
+	// Erase it from memory
+	m_objs.erase (m_objs.begin() + ulIndex);
+	
+	// Update the bounding box
+	if (this == g_curfile)
+		g_BBox.calculate ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+std::vector<partListEntry> g_PartList;
+
+void initPartList () {
+	logf ("%s: initializing parts.lst\n", __func__);
+	
+	FILE* fp = openLDrawFile ("parts.lst", false);
+	
+	if (!fp)
+		return;
+	
+	char sLine[1024];
+	while (fgets (sLine, sizeof sLine, fp)) {
+		// Locate the first whitespace
+		char* cpWhite = strstr (sLine, " ");
+		
+		char sName[65];
+		size_t uLength = (cpWhite - sLine);
+		
+		if (uLength >= 64)
+			continue; // too long
+		
+		strncpy (sName, sLine, uLength);
+		sName[uLength] = '\0';
+		
+		// Surf through the whitespace sea!
+		while (*cpWhite == ' ')
+			cpWhite++;
+		
+		// Get the end point
+		char* cpEnd = strstr (sLine, "\r");
+		
+		if (cpEnd == null) {
+			// must not be DOS-formatted
+			cpEnd = strstr (sLine, "\n");
+		}
+		
+		assert (cpEnd != null);
+		
+		// Make the file title now
+		char sTitle[81];
+		uLength = (cpEnd - cpWhite);
+		strncpy (sTitle, cpWhite, uLength);
+		sTitle[uLength] = '\0';
+		
+		// Add it to the array.
+		partListEntry entry;
+		strcpy (entry.sName, sName);
+		strcpy (entry.sTitle, sTitle);
+		g_PartList.push_back (entry);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/file.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,119 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FILE_H
+#define FILE_H
+
+#include "common.h"
+#include "ldtypes.h"
+#include "str.h"
+
+namespace LDPaths {
+	void initPaths ();
+	bool tryConfigure (str path);
+	
+	str ldconfig ();
+	str prims ();
+	str parts ();
+	str getError ();
+}
+
+// =============================================================================
+// OpenFile
+//
+// The OpenFile class stores a file opened in LDForge either as a editable file
+// for the user or for subfile caching. Its methods handle file input and output.
+// =============================================================================
+class OpenFile {
+public:
+	str m_filename, m_title;
+	vector<LDObject*> m_objs;
+	vector<LDObject*> m_objCache; // Cache of this file's contents, if desired
+	
+	int lastError;
+	
+	// Is this file implicit? Implicit files are opened automatically for
+	// caching purposes and are hidden from the user.
+	bool m_implicit;
+	
+	OpenFile ();
+	~OpenFile ();
+	
+	// Saves this file to disk.
+	bool save (str zPath = "");
+	
+	// Perform safety checks. Do this before closing any files!
+	bool safeToClose ();
+	
+	// Adds an object to this file at the end of the file.
+	ulong addObject (LDObject* obj);
+	
+	// Deletes the given object from the object chain.
+	void forgetObject (LDObject* obj);
+	
+	// At what point was this file last saved?
+	long savePos;
+	
+	LDObject* object (ulong pos) const {
+		return m_objs[pos];
+	}
+	
+	void insertObj (const ulong pos, LDObject* obj);
+};
+
+// Close all current loaded files and start off blank.
+void newFile ();
+
+// Opens the given file as the main file. Everything is closed first.
+void openMainFile (str zPath);
+
+// Finds an OpenFile by name or null if not open
+OpenFile* findLoadedFile (str name);
+
+// Opens the given file and parses the LDraw code within. Returns a pointer
+// to the opened file or null on error.
+OpenFile* openDATFile (str path, bool search);
+
+// Opens the given file and returns a pointer to it, potentially looking in /parts and /p
+FILE* openLDrawFile (str path, bool bSubDirectories);
+
+// Close all open files, whether user-opened or subfile caches.
+void closeAll ();
+
+// Parses a string line containing an LDraw object and returns the object parsed.
+LDObject* parseLine (str zLine);
+
+// Retrieves the pointer to - or loads - the given subfile.
+OpenFile* loadSubfile (str zFile);
+
+// Re-caches all subfiles.
+void reloadAllSubfiles ();
+
+typedef struct {
+	char sName[65], sTitle[81];
+} partListEntry;
+
+// Init and parse parts.lst
+void initPartList ();
+
+std::vector< LDObject* > loadFileContents (FILE* fp, ulong* numWarnings);
+
+extern vector<OpenFile*> g_loadedFiles;
+extern vector<partListEntry> g_PartList;
+
+#endif // FILE_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gldraw.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,1214 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QtGui>
+#include <QGLWidget>
+#include <GL/glu.h>
+#include "common.h"
+#include "config.h"
+#include "file.h"
+#include "gldraw.h"
+#include "bbox.h"
+#include "colors.h"
+#include "gui.h"
+#include "misc.h"
+#include "history.h"
+
+static double g_objOffset[3];
+
+static short g_pulseTick = 0;
+static const short g_numPulseTicks = 8;
+static const short g_pulseInterval = 65;
+
+static const struct staticCameraMeta {
+	const char glrotate[3];
+	const Axis axisX, axisY;
+	const bool negX, negY;
+} g_staticCameras[6] = {
+	{ { 1, 0, 0 }, X, Z, false, false },
+	{ { 0, 0, 0 }, X, Y, false, true },
+	{ { 0, 1, 0 }, Z, Y, true, true },
+	{ { -1, 0, 0 }, X, Z, false, true },
+	{ { 0, 0, 0 }, X, Y, true, true },
+	{ { 0, -1, 0 }, Z, Y, false, true },
+};
+
+cfg (str, gl_bgcolor, "#CCCCD9");
+cfg (str, gl_maincolor, "#707078");
+cfg (float, gl_maincolor_alpha, 1.0);
+cfg (int, gl_linethickness, 2);
+cfg (bool, gl_colorbfc, true);
+cfg (bool, gl_selflash, false);
+cfg (int, gl_camera, GLRenderer::Free);
+cfg (bool, gl_blackedges, true);
+cfg (bool, gl_axes, false);
+
+// CameraIcon::img is a heap-allocated QPixmap because otherwise it gets
+// initialized before program gets to main() and constructs a QApplication
+// and Qt doesn't like that.
+struct CameraIcon {
+	QPixmap* img;
+	QRect srcRect, destRect, selRect;
+	GLRenderer::Camera cam;
+} g_CameraIcons[7];
+
+const char* g_CameraNames[7] = { "Top", "Front", "Left", "Bottom", "Back", "Right", "Free" };
+
+const GLRenderer::Camera g_Cameras[7] = {
+	GLRenderer::Top,
+	GLRenderer::Front,
+	GLRenderer::Left,
+	GLRenderer::Bottom,
+	GLRenderer::Back,
+	GLRenderer::Right,
+	GLRenderer::Free
+};
+
+const struct GLAxis {
+	const QColor col;
+	const vertex vert;
+} g_GLAxes[3] = {
+	{ QColor (255, 0, 0), vertex (10000, 0, 0) },
+	{ QColor (128, 192, 0), vertex (0, 10000, 0) },
+	{ QColor (0, 160, 192), vertex (0, 0, 10000) },
+};
+	
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) {
+	resetAngles ();
+	m_picking = m_rangepick = false;
+	m_camera = (GLRenderer::Camera) gl_camera.value;
+	m_drawToolTip = false;
+	m_planeDraw = false;
+	
+	m_pulseTimer = new QTimer (this);
+	connect (m_pulseTimer, SIGNAL (timeout ()), this, SLOT (slot_timerUpdate ()));
+	
+	m_toolTipTimer = new QTimer (this);
+	m_toolTipTimer->setSingleShot (true);
+	connect (m_toolTipTimer, SIGNAL (timeout ()), this, SLOT (slot_toolTipTimer ()));
+	
+	m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
+	m_thinBorderPen = m_thickBorderPen;
+	m_thinBorderPen.setWidth (1);
+	
+	// Init camera icons
+	for (const GLRenderer::Camera cam : g_Cameras) {
+		str iconname;
+		iconname.format ("camera-%s", str (g_CameraNames[cam]).tolower ().chars ());
+		
+		CameraIcon* info = &g_CameraIcons[cam];
+		info->img = new QPixmap (getIcon (iconname));
+		info->cam = cam;
+	}
+	
+	calcCameraIconRects ();
+}
+
+// =============================================================================
+GLRenderer::~GLRenderer() {
+	for (CameraIcon& info : g_CameraIcons)
+		delete info.img;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::calcCameraIconRects () {
+	ushort i = 0;
+	
+	for (CameraIcon& info : g_CameraIcons) {
+		const long x1 = (m_width - (info.cam != Free ? 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::resetAngles () {
+	m_rotX = 30.0f;
+	m_rotY = 325.f;
+	m_panX = m_panY = m_rotZ = 0.0f;
+	
+	// Set the default zoom based on the bounding box
+	m_zoom = g_BBox.size () * 6;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::initializeGL () {
+	setBackground ();
+	
+	glEnable (GL_POLYGON_OFFSET_FILL);
+	glPolygonOffset (1.0f, 1.0f);
+	
+	glEnable (GL_DEPTH_TEST);
+	glShadeModel (GL_SMOOTH);
+	glEnable (GL_MULTISAMPLE);
+	
+	glEnable (GL_BLEND);
+	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	
+	glEnable (GL_LINE_SMOOTH);
+	glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
+	
+	glLineWidth (gl_linethickness);
+	
+	setAutoFillBackground (false);
+	setMouseTracking (true);
+	setFocusPolicy (Qt::WheelFocus);
+	compileObjects ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+QColor GLRenderer::getMainColor () {
+	QColor col (gl_maincolor.value.chars());
+	
+	if (!col.isValid ())
+		return QColor (0, 0, 0);
+	
+	col.setAlpha (gl_maincolor_alpha * 255.f);
+	return col;
+}
+
+// -----------------------------------------------------------------------------
+void GLRenderer::setBackground () {
+	QColor col (gl_bgcolor.value.chars());
+	
+	if (!col.isValid ())
+		return;
+	
+	m_darkbg = luma (col) < 80;
+	
+	col.setAlpha (255);
+	qglClearColor (col);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static vector<short> g_daWarnedColors;
+void GLRenderer::setObjectColor (LDObject* obj) {
+	QColor qcol;
+	
+	if (!obj->isColored())
+		return;
+	
+	if (m_picking) {
+		// Make the color by the object's index color if we're picking, so we can
+		// make the index from the color we get from the picking results.
+		long i = obj->getIndex (g_curfile);
+		
+		// If we couldn't find the index, this object must not be from this file,
+		// therefore it must be an object inlined from a subfile reference or
+		// decomposed from a radial. Find the top level parent object and use
+		// its index.
+		if (i == -1)
+			i = obj->topLevelParent ()->getIndex (g_curfile);
+		
+		// We should have the index now.
+		assert (i != -1);
+		
+		// Calculate a color based from this index. This method caters for
+		// 16777216 objects. I don't think that'll be exceeded anytime soon. :)
+		// ATM biggest is 53588.dat with 12600 lines.
+		double r = (i / (256 * 256)) % 256,
+			g = (i / 256) % 256,
+			b = i % 256;
+		
+		qglColor (QColor (r, g, b, 255));
+		return;
+	}
+	
+#if 0
+	if (gl_colorbfc &&
+		obj->getType () != LDObject::Line &&
+		obj->getType () != LDObject::CondLine)
+	{
+		if (bBackSide)
+			glColor4f (0.9f, 0.0f, 0.0f, 1.0f);
+		else
+			glColor4f (0.0f, 0.8f, 0.0f, 1.0f);
+		return;
+	}
+#endif
+	
+	if (obj->dColor == maincolor)
+		qcol = getMainColor ();
+	else {
+		color* col = getColor (obj->dColor);
+		qcol = col->qColor;
+	}
+	
+	if (obj->dColor == edgecolor) {
+		qcol = Qt::black;
+		color* col;
+		
+		if (!gl_blackedges && obj->parent != null && (col = getColor (obj->parent->dColor)) != null)
+			qcol = col->qEdge;
+	}
+	
+	if (qcol.isValid () == false) {
+		// The color was unknown. Use main color to make the object at least
+		// not appear pitch-black.
+		if (obj->dColor != edgecolor)
+			qcol = getMainColor ();
+		
+		// Warn about the unknown colors, but only once.
+		for (short i : g_daWarnedColors)
+			if (obj->dColor == i)
+				return;
+		
+		printf ("%s: Unknown color %d!\n", __func__, obj->dColor);
+		g_daWarnedColors.push_back (obj->dColor);
+		return;
+	}
+	
+	long r = qcol.red (),
+		g = qcol.green (),
+		b = qcol.blue (),
+		a = qcol.alpha ();
+	
+	// If it's selected, brighten it up, also pulse flash it if desired.
+	if (g_win->isSelected (obj)) {
+		short tick, numTicks;
+		
+		if (gl_selflash) {
+			tick = (g_pulseTick < (g_numPulseTicks / 2)) ? g_pulseTick : (g_numPulseTicks - g_pulseTick);
+			numTicks = g_numPulseTicks;
+		} else {
+			tick = 2;
+			numTicks = 5;
+		}
+		
+		const long add = ((tick * 128) / numTicks);
+		r = min (r + add, 255l);
+		g = min (g + add, 255l);
+		b = min (b + add, 255l);
+		
+		// a = 255;
+	}
+	
+	glColor4f (
+		((double) r) / 255.0f,
+		((double) g) / 255.0f,
+		((double) b) / 255.0f,
+		((double) a) / 255.0f);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::refresh () {
+	update ();
+	swapBuffers ();
+}
+
+// =============================================================================
+void GLRenderer::hardRefresh () {
+	compileObjects ();
+	refresh ();
+	
+	glLineWidth (gl_linethickness);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::resizeGL (int w, int h) {
+	m_width = w;
+	m_height = h;
+	
+	calcCameraIconRects ();
+	
+	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 () const {
+	if (g_curfile == null)
+		return;
+	
+	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	glEnable (GL_DEPTH_TEST);
+	
+	if (m_camera != GLRenderer::Free) {
+		glMatrixMode (GL_PROJECTION);
+		glPushMatrix ();
+		
+		glLoadIdentity ();
+		glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f);
+		glTranslatef (m_panX, m_panY, 0.0f);
+		glRotatef (90.0f, g_staticCameras[m_camera].glrotate[0],
+			g_staticCameras[m_camera].glrotate[1],
+			g_staticCameras[m_camera].glrotate[2]);
+		
+		// Back camera needs to be handled differently
+		if (m_camera == GLRenderer::Back) {
+			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 (m_panX, m_panY, -m_zoom);
+		glRotatef (m_rotX, 1.0f, 0.0f, 0.0f);
+		glRotatef (m_rotY, 0.0f, 1.0f, 0.0f);
+		glRotatef (m_rotZ, 0.0f, 0.0f, 1.0f);
+	}
+	
+	for (LDObject* obj : g_curfile->m_objs) {
+		if (obj->hidden ())
+			continue; // Don't draw hidden objects
+		
+		glCallList (m_picking == false ? obj->uGLList : obj->uGLPickList);
+	}
+	
+	if (gl_axes && !m_picking)
+		glCallList (m_axeslist);
+	
+	glPopMatrix ();
+	glMatrixMode (GL_MODELVIEW);
+}
+
+// =============================================================================
+vertex GLRenderer::coord_2to3 (const QPoint& pos2d, const bool snap) const {
+	vertex pos3d;
+	const staticCameraMeta* cam = &g_staticCameras[m_camera];
+	const Axis axisX = cam->axisX;
+	const Axis axisY = cam->axisY;
+	const short 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) - m_panX) - (negXFac * g_objOffset[axisX]);
+	double cy = (m_virtHeight - ((2 * pos2d.y () * m_virtHeight) / m_height) - m_panY) - (negYFac * g_objOffset[axisY]);
+	
+	if (snap) {
+		cx = Grid::snap (cx, (Grid::Config) axisX);
+		cy = Grid::snap (cy, (Grid::Config) axisY);
+	}
+	
+	cx *= negXFac;
+	cy *= negYFac;
+	
+	pos3d = g_origin;
+	pos3d[axisX] = cx;
+	pos3d[axisY] = cy;
+	return pos3d;
+}
+
+// =============================================================================
+QPoint GLRenderer::coord_3to2 (const vertex& pos3d) const {
+	/*
+	cx = (-m_virtWidth + ((2 * pos2d.x () * m_virtWidth) / m_width) - m_panX) - (negXFac * g_objOffset[axisX]);
+	
+	                                                 cx = (-vw + ((2 * x * vw) / w) - panx) - (neg * ofs)
+	                                  cx + (neg * ofs) = (-vw + ((2 * x * vw) / w) - panx)
+	                                  cx + (neg * ofs) = ((2 * x * vw) / w) - vw - panx
+	                    (cx + (neg * ofs)) + vw + panx = (2 * x * vw) / w
+	              ((cx + (neg * ofs)) + vw + panx) * w = 2 * vw * x
+	
+	x = (((cx + (neg * ofs)) + vw + panx) * w) / (2 * vw)
+	*/
+	
+	QPoint pos2d;
+	const staticCameraMeta* cam = &g_staticCameras[m_camera];
+	const Axis axisX = cam->axisX;
+	const Axis axisY = cam->axisY;
+	const short negXFac = cam->negX ? -1 : 1,
+		negYFac = cam->negY ? -1 : 1;
+	
+	short x1 = (((pos3d[axisX] + (negXFac * g_objOffset[axisX])) +
+		m_virtWidth + m_panX) * m_width) / (2 * m_virtWidth);
+	short y1 = -(((pos3d[axisY] + (negYFac * g_objOffset[axisY])) -
+		m_virtHeight + m_panY) * m_height) / (2 * m_virtHeight);
+	
+	x1 *= negXFac;
+	y1 *= negYFac;
+	
+	pos2d = QPoint (x1, y1);
+	return pos2d;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::paintEvent (QPaintEvent* ev) {
+	Q_UNUSED (ev)
+	m_virtWidth = m_zoom;
+	m_virtHeight = (m_height * m_virtWidth) / m_width;
+	drawGLScene ();
+	
+	QPainter paint (this);
+	QFontMetrics metrics = QFontMetrics (QFont ());
+	paint.setRenderHint (QPainter::Antialiasing);
+	
+	m_hoverpos = g_origin;
+	
+	if (m_camera != Free) {
+		// Calculate 3d position of the cursor
+		m_hoverpos = coord_2to3 (m_pos, true);
+		
+		// Paint the coordinates onto the screen.
+		str text;
+		text.format ("X: %s, Y: %s, Z: %s", ftoa (m_hoverpos[X]).chars (),
+			ftoa (m_hoverpos[Y]).chars (), ftoa (m_hoverpos[Z]).chars ());
+		
+		QFontMetrics metrics = QFontMetrics (font ());
+		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
+		
+		paint.drawText (m_width - textSize.width (), m_height - 16, textSize.width (),
+			textSize.height (), Qt::AlignCenter, text);
+		
+		// If we're plane drawing, draw the vertices onto the screen.
+		if (m_planeDraw) {
+			ushort numverts = m_planeDrawVerts.size () + 1;
+			const short blipsize = 8;
+			
+			if (numverts > 0) {
+				QPoint* poly = new QPoint[numverts];
+				
+				uchar i = 0;
+				for (vertex& vert : m_planeDrawVerts) {
+					poly[i] = coord_3to2 (vert);
+					++i;
+				}
+				
+				// Draw the cursor vertex as the last one in the list.
+				if (numverts < 5)
+					poly[i] = coord_3to2 (m_hoverpos);
+				else
+					numverts = 4;
+				
+				paint.setPen (m_thinBorderPen);
+				paint.setBrush (QColor (128, 192, 0));
+				
+				// Draw vertex blips
+				for (ushort i = 0; i < numverts; ++i) {
+					QPoint& blip = poly[i];
+					paint.drawEllipse (blip.x () - blipsize / 2, blip.y () - blipsize / 2,
+						blipsize, blipsize);
+				}
+				
+				paint.setPen (m_thickBorderPen);
+				paint.setBrush (QColor (128, 192, 0, 128));
+				paint.drawPolygon (poly, numverts);
+				
+				delete[] poly;
+			}
+		}
+	}
+	
+	// Camera icons
+	if (!m_picking && m_planeDraw == false) {
+		// Draw a background for the selected camera
+		paint.setPen (m_thinBorderPen);
+		paint.setBrush (QBrush (QColor (0, 128, 160, 128)));
+		paint.drawRect (g_CameraIcons[camera ()].selRect);
+		
+		// Draw the actual icons
+		for (CameraIcon& info : g_CameraIcons)
+			paint.drawPixmap (info.destRect, *info.img, info.srcRect);
+		
+		// Draw a label for the current camera in the top left corner
+		{
+			const ushort margin = 4;
+			
+			str label;
+			label.format ("%s Camera", g_CameraNames[camera ()]);
+			paint.setBrush (Qt::black);
+			paint.drawText (QPoint (margin, margin + metrics.ascent ()), label);
+		}
+		
+		// Tool tips
+		if (m_drawToolTip) {
+			if (g_CameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false)
+				m_drawToolTip = false;
+			else {
+				QPen bord = m_thinBorderPen;
+				bord.setBrush (Qt::black);
+				
+				const ushort margin = 2;
+				ushort x0 = m_pos.x (),
+					y0 = m_pos.y ();
+				
+				str label;
+				label.format ("%s Camera", g_CameraNames[m_toolTipCamera]);
+				
+				const ushort textWidth = metrics.width (label),
+					textHeight = metrics.height (),
+					fullWidth = textWidth + (2 * margin),
+					fullHeight = textHeight + (2 * margin);
+				
+				QRect area (m_pos.x (), m_pos.y (), fullWidth, fullHeight);
+				
+				if (x0 + fullWidth > m_width)
+					x0 -= fullWidth;
+				
+				if (y0 + fullHeight > m_height)
+					y0 -= fullHeight;
+				
+				paint.setBrush (QColor (0, 128, 255, 208));
+				paint.setPen (bord);
+				paint.drawRect (x0, y0, fullWidth, fullHeight);
+				
+				paint.setBrush (Qt::black);
+				paint.drawText (QPoint (x0 + margin, y0 + margin + metrics.ascent ()), label);
+			}
+		}
+	}
+	
+	// If we're range-picking, draw a rectangle encompassing the selection area.
+	if (m_rangepick && !m_picking) {
+		const short x0 = m_rangeStart.x (),
+			y0 = m_rangeStart.y (),
+			x1 = m_pos.x (),
+			y1 = m_pos.y ();
+		
+		QRect rect (x0, y0, x1 - x0, y1 - y0);
+		QColor fillColor = (m_addpick ? "#80FF00" : "#00CCFF");
+		fillColor.setAlphaF (0.2f);
+		
+		paint.setPen (m_thickBorderPen);
+		paint.setBrush (QBrush (fillColor));
+		paint.drawRect (rect);
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::compileObjects () {
+	if (g_BBox.empty () == false) {
+		g_objOffset[X] = -(g_BBox.v0 ()[X] + g_BBox.v1 ()[X]) / 2;
+		g_objOffset[Y] = -(g_BBox.v0 ()[Y] + g_BBox.v1 ()[Y]) / 2;
+		g_objOffset[Z] = -(g_BBox.v0 ()[Z] + g_BBox.v1 ()[Z]) / 2;
+	} else {
+		// use a default bbox if we need 
+		g_objOffset[X] = g_objOffset[Y] = g_objOffset[Z] = 0;
+	}
+	
+	if (!g_curfile) {
+		printf ("renderer: no files loaded, cannot compile anything\n");
+		return;
+	}
+	
+	for (LDObject* obj : g_curfile->m_objs) {
+		GLuint* upaLists[2] = {
+			&obj->uGLList,
+			&obj->uGLPickList
+		};
+		
+		for (GLuint* upMemberList : upaLists) {
+			GLuint uList = glGenLists (1);
+			glNewList (uList, GL_COMPILE);
+			
+			m_picking = (upMemberList == &obj->uGLPickList);
+			compileOneObject (obj);
+			m_picking = false;
+			
+			glEndList ();
+			*upMemberList = uList;
+		}
+	}
+	
+	// Compile axes
+	m_axeslist = glGenLists (1);
+	glNewList (m_axeslist, GL_COMPILE);
+	glBegin (GL_LINES);
+	for (const GLAxis& ax : g_GLAxes) {
+		qglColor (ax.col);
+		compileVertex (ax.vert);
+		compileVertex (-ax.vert);
+	}
+	glEnd ();
+	glEndList ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype) {
+	glBegin (gltype);
+	
+	const short numverts = (obj->getType () != LDObject::CondLine) ? obj->vertices () : 2;
+	
+	for (short i = 0; i < numverts; ++i)
+		compileVertex (obj->vaCoords[i]);
+	
+	glEnd ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::compileOneObject (LDObject* obj) {
+	setObjectColor (obj);
+	
+	switch (obj->getType ()) {
+	case LDObject::Line:
+		compileSubObject (obj, GL_LINES);
+		break;
+	
+	case LDObject::CondLine:
+		glLineStipple (1, 0x6666);
+		glEnable (GL_LINE_STIPPLE);
+		
+		compileSubObject (obj, GL_LINES);
+		
+		glDisable (GL_LINE_STIPPLE);
+		break;
+	
+	case LDObject::Triangle:
+		compileSubObject (obj, GL_TRIANGLES);
+		break;
+	
+	case LDObject::Quad:
+		compileSubObject (obj, GL_QUADS);
+		break;
+	
+	case LDObject::Subfile:
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			vector<LDObject*> objs = ref->inlineContents (true, true);
+			
+			for (LDObject* obj : objs) {
+				compileOneObject (obj);
+				delete obj;
+			}
+		}
+		break;
+	
+	case LDObject::Radial:
+		{
+			LDRadial* pRadial = static_cast<LDRadial*> (obj);
+			std::vector<LDObject*> objs = pRadial->decompose (true);
+			
+			for (LDObject* obj : objs) {
+				compileOneObject (obj);
+				delete obj;
+			}
+		}
+		break;
+	
+#if 0
+	TODO: find a proper way to draw vertices without having them be affected by zoom.
+	case LDObject::Vertex:
+		{
+			LDVertex* pVert = static_cast<LDVertex*> (obj);
+			LDTriangle* pPoly;
+			vertex* vPos = &(pVert->vPosition);
+			const double fPolyScale = max (fZoom, 1.0);
+			
+#define BIPYRAMID_COORD(N) ((((i + N) % 4) >= 2 ? 1 : -1) * 0.3f * fPolyScale)
+			
+			for (int i = 0; i < 8; ++i) {
+				pPoly = new LDTriangle;
+				pPoly->vaCoords[0] = {vPos->x, vPos->y + ((i >= 4 ? 1 : -1) * 0.4f * fPolyScale), vPos->z};
+				pPoly->vaCoords[1] = {
+					vPos->x + BIPYRAMID_COORD (0),
+					vPos->y,
+					vPos->z + BIPYRAMID_COORD (1)
+				};
+				
+				pPoly->vaCoords[2] = {
+					vPos->x + BIPYRAMID_COORD (1),
+					vPos->y,
+					vPos->z + BIPYRAMID_COORD (2)
+				};
+				
+				pPoly->dColor = pVert->dColor;
+				compileOneObject (pPoly);
+				delete pPoly;
+			}
+		}
+		break;
+#endif // 0
+	
+	default:
+		break;
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::compileVertex (const vertex& vrt) {
+	glVertex3d (
+		(vrt[X] + g_objOffset[0]),
+		-(vrt[Y] + g_objOffset[1]),
+		-(vrt[Z] + g_objOffset[2]));
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::clampAngle (double& angle) {
+	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) && !(ev->buttons() & Qt::LeftButton);
+	const bool wasRight = (m_lastButtons & Qt::RightButton) && !(ev->buttons() & Qt::RightButton);
+	
+	if (wasLeft) {
+		if (m_planeDraw) {
+			// If we picked an already-existing vertex, stop drawing
+			for (vertex& vert : m_planeDrawVerts) {
+				if (vert == m_hoverpos) {
+					endPlaneDraw (true);
+					return;
+				}
+			}
+			
+			// Also, if have 4 verts, also stop drawing.
+			if (m_planeDrawVerts.size () >= 4)
+				endPlaneDraw (true);
+			
+			m_planeDrawVerts.push_back (m_hoverpos);
+			
+			update ();
+			return;
+		} else {
+			if (!m_rangepick)
+				m_addpick = (m_keymods & Qt::ControlModifier);
+			
+			if (m_totalmove < 10 || m_rangepick)
+				pick (ev->x (), ev->y ());
+		}
+		
+		m_rangepick = false;
+		m_totalmove = 0;
+		return;
+	}
+	
+	if (wasRight && m_planeDraw) {
+		if (m_planeDrawVerts.size () > 0) {
+			// Remove the last vertex
+			m_planeDrawVerts.erase (m_planeDrawVerts.end () - 1);
+		} else {
+			endPlaneDraw (false);
+			return;
+		}
+		
+		update ();
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::mousePressEvent (QMouseEvent* ev) {
+	if (ev->buttons () & Qt::LeftButton)
+		m_totalmove = 0;
+	
+	if (ev->modifiers () & Qt::ShiftModifier) {
+		m_rangepick = true;
+		m_rangeStart.setX (ev->x ());
+		m_rangeStart.setY (ev->y ());
+		
+		m_addpick = (m_keymods & Qt::ControlModifier);
+	}
+	
+	m_lastButtons = ev->buttons ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::mouseMoveEvent (QMouseEvent* ev) {
+	int dx = ev->x () - m_pos.x ();
+	int dy = ev->y () - m_pos.y ();
+	m_totalmove += abs (dx) + abs (dy);
+	
+	if (ev->buttons () & Qt::LeftButton && !m_rangepick) {
+		m_rotX = m_rotX + (dy);
+		m_rotY = m_rotY + (dx);
+		
+		clampAngle (m_rotX);
+		clampAngle (m_rotY);
+	}
+	
+	if (ev->buttons () & Qt::MidButton) {
+		m_panX += 0.03f * dx * (m_zoom / 7.5f);
+		m_panY -= 0.03f * dy * (m_zoom / 7.5f);
+	}
+	
+	// Start the tool tip timer
+	if (!m_drawToolTip)
+		m_toolTipTimer->start (1000);
+	
+	m_pos = ev->pos ();
+	update ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::keyPressEvent (QKeyEvent* ev) {
+	m_keymods = ev->modifiers ();
+}
+
+void GLRenderer::keyReleaseEvent (QKeyEvent* ev) {
+	m_keymods = ev->modifiers ();
+}
+
+// =============================================================================
+void GLRenderer::wheelEvent (QWheelEvent* ev) {
+	m_zoom *= (ev->delta () < 0) ? 1.2f : (1.0f / 1.2f);
+	m_zoom = clamp (m_zoom, 0.01, 10000.0);
+	
+	update ();
+	ev->accept ();
+}
+
+// =============================================================================
+void GLRenderer::leaveEvent (QEvent* ev) {
+	Q_UNUSED (ev);
+	m_drawToolTip = false;
+	m_toolTipTimer->stop ();
+	update ();
+}
+
+// =============================================================================
+void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) {
+	g_win->spawnContextMenu (ev->globalPos ());
+}
+
+// =============================================================================
+void GLRenderer::setCamera (const GLRenderer::Camera cam) {
+	m_camera = cam;
+	gl_camera = (int) cam;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::updateSelFlash () {
+	if (gl_selflash && g_win->sel ().size() > 0) {
+		m_pulseTimer->start (g_pulseInterval);
+		g_pulseTick = 0;
+	} else
+		m_pulseTimer->stop ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::pick (uint mouseX, uint mouseY) {
+	// Check if we selected a camera icon
+	if (!m_rangepick) {
+		QPoint pos (mouseX, mouseY);
+		
+		for (CameraIcon& info : g_CameraIcons) {
+			if (info.destRect.contains (pos)) {
+				setCamera (info.cam);
+				update ();
+				return;
+			}
+		}
+	}
+	
+	GLint viewport[4];
+	LDObject* removedObject = null;
+	
+	// Clear the selection if we do not wish to add to it.
+	if (!m_addpick) {
+		std::vector<LDObject*> oldsel = g_win->sel ();
+		g_win->sel ().clear ();
+		
+		// Recompile the prior selection to remove the highlight color
+		for (LDObject* obj : oldsel)
+			recompileObject (obj);
+	}
+	
+	m_picking = true;
+	
+	// Paint the picking scene
+	glDisable (GL_DITHER);
+	glClearColor (1.0f, 1.0f, 1.0f, 1.0f);
+	drawGLScene ();
+	
+	glGetIntegerv (GL_VIEWPORT, viewport);
+	
+	short x0 = mouseX,
+		y0 = mouseY;
+	short x1, y1;
+	
+	// Determine how big an area to read - with range picking, we pick by
+	// the area given, with single pixel picking, we use an 1 x 1 area.
+	if (m_rangepick) {
+		x1 = m_rangeStart.x ();
+		y1 = m_rangeStart.y ();
+	} else {
+		x1 = x0 + 1;
+		y1 = y0 + 1;
+	}
+	
+	// x0 and y0 must be less than x1 and y1, respectively.
+	if (x0 > x1)
+		dataswap (x0, x1);
+	
+	if (y0 > y1)
+		dataswap (y0, y1);
+	
+	// Clamp the values to ensure they're within bounds
+	x0 = max<short> (0, x0);
+	y0 = max<short> (0, y0);
+	x1 = min<short> (x1, m_width);
+	y1 = min<short> (y1, m_height);
+	
+	const short areawidth = (x1 - x0);
+	const short areaheight = (y1 - y0);
+	const long numpixels = areawidth * areaheight;
+	
+	// Allocate space for the pixel data.
+	uchar* const pixeldata = new uchar[4 * numpixels];
+	uchar* pixelptr = &pixeldata[0];
+	
+	assert (viewport[3] == m_height);
+	
+	// Read pixels from the color buffer.
+	glReadPixels (x0, viewport[3] - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata);
+	
+	// Go through each pixel read and add them to the selection.
+	for (long i = 0; i < numpixels; ++i) {
+		uint32 idx =
+			(*(pixelptr) * 0x10000) +
+			(*(pixelptr + 1) * 0x00100) +
+			(*(pixelptr + 2) * 0x00001);
+		pixelptr += 4;
+		
+		if (idx == 0xFFFFFF)
+			continue; // White is background; skip
+		
+		LDObject* obj = g_curfile->object (idx);
+		
+		// If this is an additive single pick and the object is currently selected,
+		// we remove it from selection instead.
+		if (!m_rangepick && m_addpick) {
+			bool removed = false;
+			
+			for (ulong i = 0; i < g_win->sel ().size(); ++i) {
+				if (g_win->sel ()[i] == obj) {
+					g_win->sel ().erase (g_win->sel ().begin () + i);
+					removedObject = obj;
+					removed = true;
+				}
+			}
+			
+			if (removed)
+				break;
+		}
+		
+		g_win->sel ().push_back (obj);
+	}
+	
+	delete[] pixeldata;
+	
+	// Remove duplicate entries. For this to be effective, the vector must be
+	// sorted first.
+	std::vector<LDObject*>& sel = g_win->sel ();
+	std::sort (sel.begin(), sel.end ());
+	std::vector<LDObject*>::iterator pos = std::unique (sel.begin (), sel.end ());
+	sel.resize (std::distance (sel.begin (), pos));
+	
+	// Update everything now.
+	g_win->buildObjList ();
+	
+	m_picking = false;
+	m_rangepick = false;
+	glEnable (GL_DITHER);
+	
+	setBackground ();
+	updateSelFlash ();
+	
+	for (LDObject* obj : g_win->sel ())
+		recompileObject (obj);
+	
+	if (removedObject != null)
+		recompileObject (removedObject);
+	
+	drawGLScene ();
+	swapBuffers ();
+	update ();
+}
+
+// =============================================================================
+void GLRenderer::beginPlaneDraw () {
+	if (m_camera == Free)
+		return; // Cannot draw with the free camera
+	
+	m_planeDraw = true;
+	
+	// Disable the context menu - we need the right mouse button
+	// for removing vertices.
+	setContextMenuPolicy (Qt::NoContextMenu);
+	
+	// Use the crosshair cursor when plane drawing.
+	setCursor (Qt::CrossCursor);
+	
+	// Clear the selection when beginning to draw onto a plane.
+	// FIXME: make the selection clearing stuff in ::pick a method and use it
+	// here! This code doesn't update the GL lists.
+	g_win->sel ().clear ();
+	g_win->updateSelection ();
+	update ();
+}
+
+// =============================================================================
+void GLRenderer::endPlaneDraw (bool accept) {
+	// If we accepted, clean the selection and create the object
+	if (accept) {
+		vector<vertex>& verts = m_planeDrawVerts;
+		LDObject* obj = null;
+		
+		switch (verts.size ()) {
+		case 1:
+			{
+				// 1 vertex - add a vertex object
+				obj = new LDVertex;
+				static_cast<LDVertex*> (obj)->vPosition = verts[0];
+				obj->dColor = maincolor;
+			}
+			break;
+		
+		case 2:
+			{
+				// 2 verts - make a line
+				obj = new LDLine;
+				obj->dColor = edgecolor;
+				for (ushort i = 0; i < 2; ++i)
+					obj->vaCoords[i] = verts[i];
+			}
+			break;
+			
+		case 3:
+		case 4:
+			{
+				obj = (verts.size () == 3) ?
+					static_cast<LDObject*> (new LDTriangle) :
+					static_cast<LDObject*> (new LDQuad);
+				
+				obj->dColor = maincolor;
+				for (ushort i = 0; i < obj->vertices (); ++i)
+					obj->vaCoords[i] = verts[i];
+			}
+			break;
+		}
+		
+		if (obj) {
+			g_curfile->addObject (obj);
+			recompileObject (obj);
+			g_win->refresh ();
+			
+			History::addEntry (new AddHistory ({(ulong) obj->getIndex (g_curfile)}, {obj->clone ()}));
+		}
+	}
+	
+	m_planeDraw = false;
+	m_planeDrawVerts.clear ();
+	
+	unsetCursor ();
+	
+	// Restore the context menu
+	setContextMenuPolicy (Qt::DefaultContextMenu);
+	
+	update ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::recompileObject (LDObject* obj) {
+	// Replace the old list with the new one.
+	GLuint uList = glGenLists (1);
+	glNewList (uList, GL_COMPILE);
+	
+	compileOneObject (obj);
+	
+	glEndList ();
+	obj->uGLList = uList;
+}
+
+// =============================================================================
+uchar* GLRenderer::screencap (ushort& w, ushort& 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);
+	
+	// Restore the background
+	setBackground ();
+	
+	return cap;
+}
+
+// =============================================================================
+void GLRenderer::slot_timerUpdate () {
+	++g_pulseTick %= g_numPulseTicks;
+	
+	for (LDObject* obj : g_win->sel ())
+		recompileObject (obj);
+	
+	update ();
+}
+
+// =============================================================================
+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 : g_CameraIcons) {
+		if (icon.destRect.contains (m_pos)) {
+			m_toolTipCamera = icon.cam;
+			m_drawToolTip = true;
+			update ();
+			break;
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gldraw.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,115 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GLDRAW_H
+#define GLDRAW_H
+
+#include <QGLWidget>
+#include <qtimer.h>
+#include "common.h"
+#include "ldtypes.h"
+
+// =============================================================================
+// GLRenderer
+// 
+// The main renderer object, draws the brick on the screen, manages the camera
+// and selection picking. The instance of GLRenderer is accessible as
+// g_win->R ()
+// =============================================================================
+class GLRenderer : public QGLWidget {
+	Q_OBJECT
+	
+public:
+	enum Camera {
+		Top,
+		Front,
+		Left,
+		Bottom,
+		Back,
+		Right,
+		Free
+	};
+	
+	GLRenderer (QWidget* parent = null);
+	~GLRenderer ();
+	
+	void hardRefresh ();
+	void compileObjects ();
+	void setBackground ();
+	void pick (uint mouseX, uint mouseY);
+	QColor getMainColor ();
+	void recompileObject (LDObject* obj);
+	void refresh ();
+	void updateSelFlash ();
+	void resetAngles ();
+	uchar* screencap (ushort& w, ushort& h);
+	void setCamera (const GLRenderer::Camera cam);
+	void beginPlaneDraw ();
+	void endPlaneDraw (bool accept);
+	
+	GLRenderer::Camera camera () { return m_camera; }
+	bool picking () { return m_picking; }
+	void setZoom (const double zoom) { m_zoom = zoom; }
+	double zoom () const { return m_zoom; }
+
+protected:
+	void initializeGL ();
+	void resizeGL (int w, int h);
+	
+	void mousePressEvent (QMouseEvent* ev);
+	void mouseMoveEvent (QMouseEvent* ev);
+	void mouseReleaseEvent (QMouseEvent* ev);
+	void keyPressEvent (QKeyEvent* ev);
+	void keyReleaseEvent (QKeyEvent* ev);
+	void wheelEvent (QWheelEvent* ev);
+	void paintEvent (QPaintEvent* ev);
+	void leaveEvent (QEvent* ev);
+	void contextMenuEvent (QContextMenuEvent* ev);
+
+private:
+	QTimer* m_pulseTimer, *m_toolTipTimer;
+	Qt::MouseButtons m_lastButtons;
+	Qt::KeyboardModifiers m_keymods;
+	ulong m_totalmove;
+	vertex m_hoverpos;
+	double m_virtWidth, m_virtHeight, m_rotX, m_rotY, m_rotZ, m_panX, m_panY, m_zoom;
+	bool m_darkbg, m_picking, m_rangepick, m_addpick, m_drawToolTip, m_screencap, m_planeDraw;
+	QPoint m_pos, m_rangeStart;
+	QPen m_thinBorderPen, m_thickBorderPen;
+	Camera m_camera, m_toolTipCamera;
+	uint m_axeslist;
+	ushort m_width, m_height;
+	std::vector<vertex> m_planeDrawVerts;
+	
+	void compileOneObject (LDObject* obj);
+	void compileSubObject (LDObject* obj, const GLenum gltype);
+	void compileVertex (const vertex& vrt);
+	void clampAngle (double& angle);
+	void setObjectColor (LDObject* obj);
+	void drawGLScene () const;
+	void calcCameraIconRects ();
+	
+	vertex coord_2to3 (const QPoint& pos2d, const bool snap) const;
+	QPoint coord_3to2 (const vertex& pos3d) const;
+	
+private slots:
+	void slot_timerUpdate ();
+	void slot_toolTipTimer ();
+};
+
+#endif // GLDRAW_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,1025 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <qgridlayout.h>
+#include <qmessagebox.h>
+#include <qevent.h>
+#include <qmenubar.h>
+#include <qstatusbar.h>
+#include <qsplitter.h>
+#include <qlistwidget.h>
+#include <qtoolbutton.h>
+#include <qcombobox.h>
+#include <qcoreapplication.h>
+#include "common.h"
+#include "gldraw.h"
+#include "gui.h"
+#include "file.h"
+#include "config.h"
+#include "misc.h"
+#include "colors.h"
+#include "history.h"
+#include "config.h"
+
+vector<actionmeta> g_ActionMeta;
+
+static bool g_bSelectionLocked = false;
+
+cfg (bool, lv_colorize, true);
+cfg (int, gui_toolbar_iconsize, 24);
+cfg (str, gui_colortoolbar, "16:24:|:0:1:2:3:4:5:6:7");
+extern_cfg (str, io_recentfiles);
+extern_cfg (bool, gl_axes);
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+ForgeWindow::ForgeWindow () {
+	g_win = this;
+	m_renderer = new GLRenderer;
+	
+	m_objList = new ObjectList;
+	m_objList->setSelectionMode (QListWidget::ExtendedSelection);
+	m_objList->setAlternatingRowColors (true);
+	connect (m_objList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_selectionChanged ()));
+	
+	m_splitter = new QSplitter;
+	m_splitter->addWidget (m_renderer);
+	m_splitter->addWidget (m_objList);
+	setCentralWidget (m_splitter);
+	
+	m_colorMeta = parseQuickColorMeta ();
+	
+	createMenuActions ();
+	createMenus ();
+	createToolbars ();
+	
+	slot_selectionChanged ();
+	
+	setStatusBar (new QStatusBar);
+	setWindowIcon (getIcon ("ldforge"));
+	setTitle ();
+	setMinimumSize (320, 200);
+	resize (800, 600);
+	
+	connect (QCoreApplication::instance (), SIGNAL (aboutToQuit ()), this, SLOT (slot_lastSecondCleanup ()));
+}
+
+// =============================================================================
+void ForgeWindow::slot_lastSecondCleanup () {
+	m_renderer->setParent (null);
+	delete m_renderer;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::createMenuActions () {
+	// Create the actions based on stored meta.
+	for (actionmeta meta : g_ActionMeta) {
+		QAction*& qAct = *meta.qAct;
+		qAct = new QAction (getIcon (meta.sIconName), meta.sDisplayName, this);
+		qAct->setStatusTip (meta.sDescription);
+		qAct->setShortcut (*meta.conf);
+		
+		connect (qAct, SIGNAL (triggered ()), this, SLOT (slot_action ()));
+	}
+	
+	// Grid actions and axes are checkable
+	findAction ("gridCoarse")->setCheckable (true);
+	findAction ("gridMedium")->setCheckable (true);
+	findAction ("gridFine")->setCheckable (true);
+	
+	findAction ("axes")->setCheckable (true);
+	findAction ("axes")->setChecked (gl_axes);
+	
+	// things not implemented yet
+	findAction ("help")->setEnabled (false);
+	
+	History::updateActions ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+QMenu* g_CurrentMenu;
+
+QMenu* ForgeWindow::initMenu (const char* name) {
+	return g_CurrentMenu = menuBar ()->addMenu (tr (name));
+}
+
+void ForgeWindow::addMenuAction (const char* name) {
+	g_CurrentMenu->addAction (findAction (name));
+}
+
+
+// =============================================================================
+void ForgeWindow::createMenus () {
+	m_recentFilesMenu = new QMenu (tr ("Open &Recent"));
+	m_recentFilesMenu->setIcon (getIcon ("open-recent"));
+	updateRecentFilesMenu ();
+	
+	QMenu*& menu = g_CurrentMenu;
+	
+	// File menu
+	initMenu ("&File");
+	addMenuAction ("newFile");				// New
+	addMenuAction ("open");				// Open
+	menu->addMenu (m_recentFilesMenu);	// Open Recent
+	addMenuAction ("save");				// Save
+	addMenuAction ("saveAs");				// Save As
+	menu->addSeparator ();					// -------
+	addMenuAction ("settings");			// Settings
+	addMenuAction ("setLDrawPath");		// Set LDraw Path
+	menu->addSeparator ();					// -------
+	addMenuAction ("exit");				// Exit
+	
+	// View menu
+	initMenu ("&View");
+	addMenuAction ("resetView");			// Reset View
+	addMenuAction ("axes");				// Draw Axes
+	menu->addSeparator ();					// -----
+	addMenuAction ("screencap");			// Screencap Part
+	addMenuAction ("showHistory");		// Edit History
+	
+	// Insert menu
+	initMenu ("&Insert");
+	addMenuAction ("insertFrom");			// Insert from File
+	addMenuAction ("insertRaw");			// Insert Raw
+	menu->addSeparator ();					// -------
+	addMenuAction ("newSubfile");			// New Subfile
+	addMenuAction ("newLine");			// New Line
+	addMenuAction ("newTriangle");		// New Triangle
+	addMenuAction ("newQuad");				// New Quad
+	addMenuAction ("newCondLine");		// New Conditional Line
+	addMenuAction ("newComment");			// New Comment
+	addMenuAction ("newBFC");				// New BFC Statment
+	addMenuAction ("newVertex");			// New Vertex
+	addMenuAction ("newRadial");			// New Radial
+	menu->addSeparator ();					// -----
+	addMenuAction ("beginDraw");			// Begin Drawing
+	addMenuAction ("doneDraw");			// Cancel Drawing
+	addMenuAction ("cancelDraw");			// Done Drawing
+	
+	// Edit menu
+	initMenu ("&Edit");
+	addMenuAction ("undo");				// Undo
+	addMenuAction ("redo");				// Redo
+	menu->addSeparator ();					// -----
+	addMenuAction ("cut");					// Cut
+	addMenuAction ("copy");				// Copy
+	addMenuAction ("paste");				// Paste
+	addMenuAction ("del");					// Delete
+	menu->addSeparator ();					// -----
+	
+	addMenuAction ("selectAll");			// Select All
+	addMenuAction ("selectByColor");		// Select by Color
+	addMenuAction ("selectByType");		// Select by Type
+	
+	initMenu ("&Tools");
+	addMenuAction ("setColor");			// Set Color
+	addMenuAction ("invert");				// Invert
+	addMenuAction ("inlineContents");		// Inline
+	addMenuAction ("deepInline");			// Deep Inline
+	addMenuAction ("splitQuads");			// Split Quads
+	addMenuAction ("setContents");		// Set Contents
+	addMenuAction ("makeBorders");		// Make Borders
+	addMenuAction ("makeCornerVerts");	// Make Corner Vertices
+	addMenuAction ("roundCoords");		// Round Coordinates
+	addMenuAction ("uncolorize");			// Uncolorize
+	addMenuAction ("visibility");			// Toggle Visibility
+	
+	// Move menu
+	initMenu ("&Move");
+	addMenuAction ("moveUp");				// Move Up
+	addMenuAction ("moveDown");			// Move Down
+	menu->addSeparator ();					// -----
+	addMenuAction ("gridCoarse");			// Coarse Grid
+	addMenuAction ("gridMedium");			// Medium Grid
+	addMenuAction ("gridFine");			// Fine Grid
+	menu->addSeparator ();					// -----
+	addMenuAction ("moveXPos");			// Move +X
+	addMenuAction ("moveXNeg");			// Move -X
+	addMenuAction ("moveYPos");			// Move +Y
+	addMenuAction ("moveYNeg");			// Move -Y
+	addMenuAction ("moveZPos");			// Move +Z
+	addMenuAction ("moveZNeg");			// Move -Z
+	menu->addSeparator ();					// -----
+	addMenuAction ("rotateXPos");			// Rotate +X
+	addMenuAction ("rotateXNeg");			// Rotate -X
+	addMenuAction ("rotateYPos");			// Rotate +Y
+	addMenuAction ("rotateYNeg");			// Rotate -Y
+	addMenuAction ("rotateZPos");			// Rotate +Z
+	addMenuAction ("rotateZNeg");			// Rotate -Z
+	
+	initMenu ("E&xternal Programs");
+	addMenuAction ("ytruder");
+	addMenuAction ("rectifier");
+	addMenuAction ("intersector");
+	
+	// Help menu
+	initMenu ("&Help");
+	addMenuAction ("help");				// Help
+	menu->addSeparator ();					// -----
+	addMenuAction ("about");				// About
+	addMenuAction ("aboutQt");				// About Qt
+}
+
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::updateRecentFilesMenu () {
+	// First, clear any items in the recent files menu
+	for (QAction* recent : m_recentFiles)
+		delete recent;
+	m_recentFiles.clear ();
+	
+	std::vector<str> files = io_recentfiles.value / "@";
+	for (long i = files.size() - 1; i >= 0; --i) {
+		str file = files[i];
+		
+		QAction* recent = new QAction (getIcon ("open-recent"), file, this);
+		
+		connect (recent, SIGNAL (triggered ()), this, SLOT (slot_recentFile ()));
+		m_recentFilesMenu->addAction (recent);
+		m_recentFiles.push_back (recent);
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static QToolBar* g_CurrentToolBar;
+static Qt::ToolBarArea g_ToolBarArea = Qt::TopToolBarArea;
+
+void ForgeWindow::initSingleToolBar (const char* name) {
+	QToolBar* toolbar = new QToolBar (name);
+	addToolBar (g_ToolBarArea, toolbar);
+	m_toolBars.push_back (toolbar);
+	
+	g_CurrentToolBar = toolbar;
+}
+
+// =============================================================================
+void ForgeWindow::addToolBarAction (const char* name) {
+	g_CurrentToolBar->addAction (findAction (name));
+}
+
+// =============================================================================
+void ForgeWindow::createToolbars () {
+	initSingleToolBar ("File");
+	addToolBarAction ("newFile");
+	addToolBarAction ("open");
+	addToolBarAction ("save");
+	addToolBarAction ("saveAs");
+	
+	// ==========================================
+	initSingleToolBar ("Insert");
+	addToolBarAction ("newSubfile");
+	addToolBarAction ("newLine");
+	addToolBarAction ("newTriangle");
+	addToolBarAction ("newQuad");
+	addToolBarAction ("newCondLine");
+	addToolBarAction ("newComment");
+	addToolBarAction ("newBFC");
+	addToolBarAction ("newVertex");
+	addToolBarAction ("newRadial");
+	
+	// ==========================================
+	initSingleToolBar ("Edit");
+	addToolBarAction ("undo");
+	addToolBarAction ("redo");
+	addToolBarAction ("cut");
+	addToolBarAction ("copy");
+	addToolBarAction ("paste");
+	addToolBarAction ("del");
+	
+	// ==========================================
+	initSingleToolBar ("Select");
+	addToolBarAction ("selectAll");
+	addToolBarAction ("selectByColor");
+	addToolBarAction ("selectByType");
+	
+	addToolBarBreak (Qt::TopToolBarArea);
+	
+	// ==========================================
+	initSingleToolBar ("Move");
+	addToolBarAction ("moveUp");
+	addToolBarAction ("moveDown");
+	addToolBarAction ("moveXPos");
+	addToolBarAction ("moveXNeg");
+	addToolBarAction ("moveYPos");
+	addToolBarAction ("moveYNeg");
+	addToolBarAction ("moveZPos");
+	addToolBarAction ("moveZNeg");
+	
+	// ==========================================
+	initSingleToolBar ("Rotate");
+	addToolBarAction ("rotateXPos");
+	addToolBarAction ("rotateXNeg");
+	addToolBarAction ("rotateYPos");
+	addToolBarAction ("rotateYNeg");
+	addToolBarAction ("rotateZPos");
+	addToolBarAction ("rotateZNeg");
+	
+	// ==========================================
+	// Grid toolbar
+	initSingleToolBar ("Grids");
+	addToolBarAction ("gridCoarse");
+	addToolBarAction ("gridMedium");
+	addToolBarAction ("gridFine");
+	addToolBarBreak (Qt::TopToolBarArea);
+	
+	// ==========================================
+	initSingleToolBar ("View");
+	addToolBarAction ("axes");
+	
+	// ==========================================
+	// Color toolbar
+	m_colorToolBar = new QToolBar ("Quick Colors");
+	addToolBar (Qt::RightToolBarArea, m_colorToolBar);
+	
+	// ==========================================
+	// Left area toolbars
+	//g_ToolBarArea = Qt::LeftToolBarArea;
+	initSingleToolBar ("Tools");
+	addToolBarAction ("setColor");
+	addToolBarAction ("invert");
+	addToolBarAction ("inlineContents");
+	addToolBarAction ("deepInline");
+	addToolBarAction ("splitQuads");
+	addToolBarAction ("setContents");
+	addToolBarAction ("makeBorders");
+	addToolBarAction ("makeCornerVerts");
+	addToolBarAction ("roundCoords");
+	addToolBarAction ("screencap");
+	addToolBarAction ("uncolorize");
+	addToolBarAction ("visibility");
+	
+	initSingleToolBar ("External Programs");
+	addToolBarAction ("ytruder");
+	addToolBarAction ("rectifier");
+	addToolBarAction ("intersector");
+	updateToolBars ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+std::vector<quickColorMetaEntry> parseQuickColorMeta () {
+	std::vector<quickColorMetaEntry> meta;
+	
+	for (str colorname : gui_colortoolbar.value / ":") {
+		if (colorname == "|") {
+			meta.push_back ({null, null, true});
+		} else {
+			color* col = getColor (atoi (colorname));
+			assert (col != null);
+			meta.push_back ({col, null, false});
+		}
+	}
+	
+	return meta;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::updateToolBars () {
+	for (QToolBar* bar : m_toolBars)
+		bar->setIconSize (QSize (gui_toolbar_iconsize, gui_toolbar_iconsize));
+	
+	// Update the quick color toolbar.
+	for (QPushButton* btn : m_colorButtons)
+		delete btn;
+	
+	m_colorButtons.clear ();
+	
+	// Clear the toolbar to remove separators
+	m_colorToolBar->clear ();
+	
+	for (quickColorMetaEntry& entry : m_colorMeta) {
+		if (entry.bSeparator)
+			m_colorToolBar->addSeparator ();
+		else {
+			QPushButton* colorButton = new QPushButton;
+			colorButton->setAutoFillBackground (true);
+			colorButton->setStyleSheet (fmt ("background-color: %s", entry.col->zColorString.chars()));
+			colorButton->setToolTip (entry.col->zName);
+			
+			connect (colorButton, SIGNAL (clicked ()), this, SLOT (slot_quickColor ()));
+			m_colorToolBar->addWidget (colorButton);
+			m_colorButtons.push_back (colorButton);
+			
+			entry.btn = colorButton;
+		}
+	}
+	
+	updateGridToolBar ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::updateGridToolBar () {
+	// Ensure that the current grid - and only the current grid - is selected.
+	findAction ("gridCoarse")->setChecked (grid == Grid::Coarse);
+	findAction ("gridMedium")->setChecked (grid == Grid::Medium);
+	findAction ("gridFine")->setChecked (grid == Grid::Fine);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::setTitle () {
+	str title = APPNAME " v";
+	title += versionString;
+	
+	// Append our current file if we have one
+	if (g_curfile) {
+		title += fmt (": %s", basename (g_curfile->m_filename.chars()));
+		
+		if (g_curfile->m_objs.size() > 0 &&
+			g_curfile->m_objs[0]->getType() == LDObject::Comment)
+		{
+			// Append title
+			LDComment* comm = static_cast<LDComment*> (g_curfile->m_objs[0]);
+			title += fmt (": %s", comm->text.chars());
+		}
+		
+		if (History::pos () != g_curfile->savePos)
+			title += '*';
+	}
+	
+	setWindowTitle (title);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::slot_action () {
+	// Get the action that triggered this slot.
+	QAction* qAct = static_cast<QAction*> (sender ());
+	
+	// Find the meta for the action.
+	actionmeta* pMeta = null;
+	
+	for (actionmeta meta : g_ActionMeta) {
+		if (*meta.qAct == qAct) {
+			pMeta = &meta;
+			break;
+		}
+	}
+	
+	if (!pMeta) {
+		logf (LOG_Warning, "unknown signal sender %p!\n", qAct);
+		return;
+	}
+	
+	// We have the meta, now call the handler.
+	(*pMeta->handler) ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::deleteSelection (vector<ulong>* ulapIndices, std::vector<LDObject*>* papObjects) {
+	if (m_sel.size () == 0)
+		return;
+	
+	std::vector<LDObject*> selCopy = m_sel;
+	
+	// Delete the objects that were being selected
+	for (LDObject* obj : selCopy) {
+		if (papObjects && ulapIndices) {
+			papObjects->push_back (obj->clone ());
+			ulapIndices->push_back (obj->getIndex (g_curfile));
+		}
+		
+		g_curfile->forgetObject (obj);
+		delete obj;
+	}
+	
+	refresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::buildObjList () {
+	if (!g_curfile)
+		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_bSelectionLocked = true;
+	
+	m_objList->clear ();
+	
+	for (LDObject* obj : g_curfile->m_objs) {
+		str descr;
+		switch (obj->getType ()) {
+		case LDObject::Comment:
+			descr = static_cast<LDComment*> (obj)->text.chars();
+			
+			// Remove leading whitespace
+			while (~descr && descr[0] == ' ')
+				descr -= -1;
+			break;
+		
+		case LDObject::Empty:
+			break; // leave it empty
+		
+		case LDObject::Line:
+			{
+				LDLine* line = static_cast<LDLine*> (obj);
+				descr.format ("%s, %s",
+					line->vaCoords[0].stringRep (true).chars(),
+					line->vaCoords[1].stringRep (true).chars());
+			}
+			break;
+		
+		case LDObject::Triangle:
+			{
+				LDTriangle* triangle = static_cast<LDTriangle*> (obj);
+				descr.format ("%s, %s, %s",
+					triangle->vaCoords[0].stringRep (true).chars(),
+					triangle->vaCoords[1].stringRep (true).chars(),
+					triangle->vaCoords[2].stringRep (true).chars());
+			}
+			break;
+		
+		case LDObject::Quad:
+			{
+				LDQuad* quad = static_cast<LDQuad*> (obj);
+				descr.format ("%s, %s, %s, %s",
+					quad->vaCoords[0].stringRep (true).chars(),
+					quad->vaCoords[1].stringRep (true).chars(),
+					quad->vaCoords[2].stringRep (true).chars(),
+					quad->vaCoords[3].stringRep (true).chars());
+			}
+			break;
+		
+		case LDObject::CondLine:
+			{
+				LDCondLine* line = static_cast<LDCondLine*> (obj);
+				descr.format ("%s, %s, %s, %s",
+					line->vaCoords[0].stringRep (true).chars(),
+					line->vaCoords[1].stringRep (true).chars(),
+					line->vaCoords[2].stringRep (true).chars(),
+					line->vaCoords[3].stringRep (true).chars());
+			}
+			break;
+		
+		case LDObject::Gibberish:
+			descr.format ("ERROR: %s",
+				static_cast<LDGibberish*> (obj)->zContents.chars());
+			break;
+		
+		case LDObject::Vertex:
+			descr.format ("%s", static_cast<LDVertex*> (obj)->vPosition.stringRep (true).chars());
+			break;
+		
+		case LDObject::Subfile:
+			{
+				LDSubfile* ref = static_cast<LDSubfile*> (obj);
+				
+				descr.format ("%s %s, (",
+					ref->zFileName.chars(), ref->vPosition.stringRep (true).chars());
+				
+				for (short i = 0; i < 9; ++i)
+					descr.appendformat ("%s%s",
+						ftoa (ref->mMatrix[i]).chars(),
+						(i != 8) ? " " : "");
+				
+				descr += ')';
+			}
+			break;
+		
+		case LDObject::BFC:
+			{
+				LDBFC* bfc = static_cast<LDBFC*> (obj);
+				descr = LDBFC::statements[bfc->type];
+			}
+			break;
+		
+		case LDObject::Radial:
+			{
+				LDRadial* pRad = static_cast<LDRadial*> (obj);
+				descr.format ("%d / %d %s", pRad->dSegments, pRad->dDivisions, pRad->radialTypeName());
+				
+				if (pRad->eRadialType == LDRadial::Ring || pRad->eRadialType == LDRadial::Cone)
+					descr.appendformat (" %d", pRad->dRingNum);
+				
+				descr.appendformat (" %s", pRad->vPosition.stringRep (true).chars ());
+			}
+			break;
+		
+		default:
+			descr = g_saObjTypeNames[obj->getType ()];
+			break;
+		}
+		
+		// Put it into brackets if it's hidden
+		if (obj->hidden ()) {
+			str copy = descr.chars ();
+			descr.format ("[[ %s ]]", copy.chars ());
+		}
+		
+		QListWidgetItem* item = new QListWidgetItem (descr.chars());
+		item->setIcon (getIcon (g_saObjTypeIcons[obj->getType ()]));
+		
+		// Color gibberish orange on red so it stands out.
+		if (obj->getType() == LDObject::Gibberish) {
+			item->setBackground (QColor ("#AA0000"));
+			item->setForeground (QColor ("#FFAA00"));
+		} else if (lv_colorize && obj->isColored () &&
+			obj->dColor != maincolor && obj->dColor != edgecolor)
+		{
+			// If the object isn't in the main or edge color, draw this
+			// list entry in said color.
+			color* col = getColor (obj->dColor);
+			if (col)
+				item->setForeground (col->qColor);
+		}
+		
+		obj->qObjListEntry = item;
+		m_objList->insertItem (m_objList->count (), item);
+	}
+	
+	g_bSelectionLocked = false;
+	updateSelection ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::scrollToSelection () {
+	if (m_sel.size() == 0)
+		return;
+	
+	LDObject* obj = m_sel[m_sel.size () - 1];
+	m_objList->scrollToItem (obj->qObjListEntry);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::slot_selectionChanged () {
+	if (g_bSelectionLocked == true || g_curfile == null)
+		return;
+	
+	/*
+	// If the selection isn't 1 exact, disable setting contents
+	findAction ("setContents")->setEnabled (qObjList->selectedItems().size() == 1);
+	
+	// If we have no selection, disable splitting quads
+	findAction ("splitQuads")->setEnabled (qObjList->selectedItems().size() > 0);
+	*/
+	
+	// Update the shared selection array, though don't do this if this was
+	// called during GL picking, in which case the GL renderer takes care
+	// of the selection.
+	if (m_renderer->picking ())
+		return;
+	
+	std::vector<LDObject*> priorSelection = m_sel;
+	
+	// Get the objects from the object list selection
+	m_sel.clear ();	
+	const QList<QListWidgetItem*> items = m_objList->selectedItems ();
+	
+	for (LDObject* obj : g_curfile->m_objs)
+	for (QListWidgetItem* qItem : items) {
+		if (qItem == obj->qObjListEntry) {
+			m_sel.push_back (obj);
+			break;
+		}
+	}
+	
+	// Update the GL renderer
+	for (LDObject* obj : m_sel)
+		m_renderer->recompileObject (obj);
+	
+	for (LDObject* obj : priorSelection)
+		m_renderer->recompileObject (obj);
+	
+	m_renderer->updateSelFlash ();
+	m_renderer->refresh ();
+}
+
+// =============================================================================
+void ForgeWindow::slot_recentFile () {
+	QAction* qAct = static_cast<QAction*> (sender ());
+	openMainFile (qAct->text ());
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::slot_quickColor () {
+	QPushButton* button = static_cast<QPushButton*> (sender ());
+	color* col = null;
+	
+	for (quickColorMetaEntry entry : m_colorMeta) {
+		if (entry.btn == button) {
+			col = entry.col;
+			break;
+		}
+	}
+	
+	if (col == null)
+		return;
+	
+	std::vector<ulong> indices;
+	std::vector<short> colors;
+	short newColor = col->index ();
+	
+	for (LDObject* obj : m_sel) {
+		if (obj->dColor == -1)
+			continue; // uncolored object
+		
+		indices.push_back (obj->getIndex (g_curfile));
+		colors.push_back (obj->dColor);
+		
+		obj->dColor = newColor;
+	}
+	
+	History::addEntry (new SetColorHistory (indices, colors, newColor));
+	refresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+ulong ForgeWindow::getInsertionPoint () {
+	if (m_sel.size () > 0) {
+		// If we have a selection, put the item after it.
+		return (m_sel[m_sel.size() - 1]->getIndex (g_curfile)) + 1;
+	}
+	
+	// Otherwise place the object at the end.
+	return g_curfile->m_objs.size();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::refresh () {
+	buildObjList ();
+	m_renderer->hardRefresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::updateSelection () {
+	g_bSelectionLocked = true;
+	
+	m_objList->clearSelection ();
+	for (LDObject* obj : m_sel)
+		obj->qObjListEntry->setSelected (true);
+	
+	g_bSelectionLocked = false;
+	slot_selectionChanged ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+bool ForgeWindow::isSelected (LDObject* obj) {
+	LDObject* needle = obj->topLevelParent ();
+	
+	if (needle == null)
+		needle = obj;
+	
+	for (LDObject* hay : m_sel)
+		if (hay == needle)
+			return true;
+	
+	return false;
+}
+
+short ForgeWindow::getSelectedColor() {
+	short result = -1;
+	
+	for (LDObject* obj : m_sel) {
+		if (obj->dColor == -1)
+			continue; // doesn't use color
+		
+		if (result != -1 && obj->dColor != result)
+			return -1; // No consensus in object color
+		
+		if (result == -1)
+			result = obj->dColor;
+	}
+	
+	return result;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+LDObject::Type ForgeWindow::uniformSelectedType () {
+	LDObject::Type eResult = LDObject::Unidentified;
+	
+	for (LDObject* obj : m_sel) {
+		if (eResult != LDObject::Unidentified && obj->dColor != eResult)
+			return LDObject::Unidentified;
+		
+		if (eResult == LDObject::Unidentified)
+			eResult = obj->getType ();
+	}
+	
+	return eResult;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::closeEvent (QCloseEvent* ev) {
+	// Check whether it's safe to close all files.
+	for (OpenFile* f : g_loadedFiles) {
+		if (!f->safeToClose ()) {
+			ev->ignore ();
+			return;
+		}
+	}
+	
+	// Save the configuration before leaving so that, for instance, grid choice
+	// is preserved across instances.
+	config::save ();
+	
+	ev->accept ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void ForgeWindow::spawnContextMenu (const QPoint pos) {
+	const bool single = (g_win->sel ().size () == 1);
+	LDObject* singleObj = (single) ? g_win->sel ()[0] : null;
+	
+	QMenu* contextMenu = new QMenu;
+	
+	if (single && singleObj->getType () != LDObject::Empty) {
+		contextMenu->addAction (findAction ("editObject"));
+		contextMenu->addSeparator ();
+	}
+	
+	contextMenu->addAction (findAction ("cut"));
+	contextMenu->addAction (findAction ("copy"));
+	contextMenu->addAction (findAction ("paste"));
+	contextMenu->addAction (findAction ("del"));
+	contextMenu->addSeparator ();
+	contextMenu->addAction (findAction ("setColor"));
+	if (single)
+		contextMenu->addAction (findAction ("setContents"));
+	contextMenu->addAction (findAction ("makeBorders"));
+	
+	contextMenu->exec (pos);
+}
+
+// ========================================================================================================================================
+DelHistory* ForgeWindow::deleteObjVector (const std::vector<LDObject*> objs) {
+	vector<ulong> indices;
+	vector<LDObject*> cache;
+	
+	for (LDObject* obj : objs) {
+		indices.push_back (obj->getIndex (g_curfile));
+		cache.push_back (obj->clone ());
+		
+		g_curfile->forgetObject (obj);
+		delete obj;
+	}
+	
+	if (indices.size () > 0)
+		return new DelHistory (indices, cache);
+	
+	return null;
+}
+
+// ========================================================================================================================================
+DelHistory* ForgeWindow::deleteSelection () {
+	return deleteObjVector (sel ());
+}
+
+// ========================================================================================================================================
+DelHistory* ForgeWindow::deleteByColor (const short colnum) {
+	vector<LDObject*> objs;
+	for (LDObject* obj : g_curfile->m_objs) {
+		if (!obj->isColored () || obj->dColor != colnum)
+			continue;
+		
+		objs.push_back (obj);
+	}
+	
+	return deleteObjVector (objs);
+}
+
+// ========================================================================================================================================
+void ObjectList::contextMenuEvent (QContextMenuEvent* ev) {
+	g_win->spawnContextMenu (ev->globalPos ());
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+QPixmap getIcon (const char* iconName) {
+	return (QPixmap (fmt (":/icons/%s.png", iconName)));
+}
+
+// =============================================================================
+bool confirm (str msg) {
+	return confirm ("Confirm", msg);
+}
+
+bool confirm (str title, str msg) {
+	return QMessageBox::question (g_win, title, msg,
+		(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes;
+}
+
+// =============================================================================
+void critical (str msg) {
+	QMessageBox::critical (g_win, "Error", msg,
+		(QMessageBox::Close), QMessageBox::Close);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+// Print to message log
+// TODO: I don't think that the message log being a widget in the window
+// is a very good idea... maybe this should log into the renderer? Or into
+// another dialog that can be popped up?
+void ForgeWindow::logVA (LogType type, const char* fmtstr, va_list va) {
+	(void) type;
+	(void) fmtstr;
+	(void) va;
+}
+
+// =============================================================================
+QAction* findAction (str name) {
+	for (actionmeta& meta : g_ActionMeta)
+		if (name == meta.name)
+			return *meta.qAct;
+	
+	fprintf (stderr, "%s: couldn't find action named `%s'!\n", __func__, name.chars ());
+	assert (false);
+	return null;
+}
+
+// =============================================================================
+void makeColorSelector (QComboBox* box) {
+	std::map<short, ulong> counts;
+	
+	for (LDObject* obj : g_curfile->m_objs) {
+		if (!obj->isColored ())
+			continue;
+		
+		if (counts.find (obj->dColor) == counts.end ())
+			counts[obj->dColor] = 1;
+		else
+			counts[obj->dColor]++;
+	}
+	
+	box->clear ();
+	ulong row = 0;
+	for (const auto& pair : counts) {
+		const ushort size = 16;
+		color* col = getColor (pair.first);
+		assert (col != null);
+		
+		// Paint an icon for this color
+		QIcon ico;
+		QImage img (size, size, QImage::Format_ARGB32);
+		QPainter paint (&img);
+		paint.fillRect (QRect (0, 0, size, size), Qt::black);
+		paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8));
+		paint.fillRect (QRect (1, 1, size - 2, size - 2), QColor (col->qColor));
+		ico = QIcon (QPixmap::fromImage (img));
+		
+		box->addItem (ico, fmt ("[%d] %s (%lu object%s)",
+			pair.first, col->zName.chars (), pair.second, PLURAL (pair.second)));
+		box->setItemData (row, pair.first);
+		
+		++row;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,253 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_H
+#define GUI_H
+
+#include <QMainWindow>
+#include <QMenu>
+#include <QToolBar>
+#include <QAction>
+#include <QToolBar>
+#include <QTextEdit>
+#include <qpushbutton.h>
+#include <qlistwidget.h>
+#include <qlabel.h>
+#include <qboxlayout.h>
+#include "gldraw.h"
+#include "config.h"
+
+class QComboBox;
+class ForgeWindow;
+class color;
+class QSplitter;
+class DelHistory;
+
+// Stuff for dialogs
+#define IMPLEMENT_DIALOG_BUTTONS \
+	bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); \
+	connect (bbx_buttons, SIGNAL (accepted ()), this, SLOT (accept ())); \
+	connect (bbx_buttons, SIGNAL (rejected ()), this, SLOT (reject ())); \
+
+// =============================================================================
+// Metadata for actions
+typedef struct {
+	QAction** const qAct;
+	keyseqconfig* const conf;
+	const char* const name, *sDisplayName, *sIconName, *sDescription;
+	void (*const handler) ();
+} actionmeta;
+
+extern vector<actionmeta> g_ActionMeta;
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+#define MAKE_ACTION(NAME, DISPLAYNAME, ICONNAME, DESCR, DEFSHORTCUT) \
+	QAction* ACTION (NAME); \
+	cfg (keyseq, key_##NAME, DEFSHORTCUT); \
+	static void actionHandler_##NAME (); \
+	static ActionAdder ActionAdderInstance_##NAME (&ACTION(NAME), DISPLAYNAME, \
+		ICONNAME, DESCR, &key_##NAME, actionHandler_##NAME, #NAME); \
+	static void actionHandler_##NAME ()
+
+#define EXTERN_ACTION(NAME) extern QAction* ACTION (NAME);
+#define ACTION(N) LDForgeAction_##N
+
+// Convenience macros for key sequences.
+#define KEY(N) (Qt::Key_##N)
+#define CTRL(N) (Qt::CTRL | Qt::Key_##N)
+#define SHIFT(N) (Qt::SHIFT | Qt::Key_##N)
+#define CTRL_SHIFT(N) (Qt::CTRL | Qt::SHIFT | Qt::Key_##N)
+
+// =============================================================================
+typedef struct {
+	color* col;
+	QPushButton* btn;
+	bool bSeparator;
+} quickColorMetaEntry;
+
+// =============================================================================
+// ActionAdder
+//
+// The ACTION macro expands into - among other stuff - into an instance of this.
+// This class' constructor creates meta for the newly defined action and stores
+// it in g_ActionMeta. It is not supposed to be used directly!
+// =============================================================================
+class ActionAdder {
+public:
+	ActionAdder (QAction** qAct, const char* sDisplayName, const char* sIconName,
+		const char* sDescription, keyseqconfig* conf, void (*const handler) (),
+		const char* name)
+	{
+		actionmeta meta = {qAct, conf, name, sDisplayName, sIconName, sDescription, handler};
+		g_ActionMeta.push_back (meta);
+	}
+};
+
+// =============================================================================
+// ObjectList
+// 
+// Object list class for ForgeWindow
+// =============================================================================
+class ObjectList : public QListWidget {
+	Q_OBJECT
+	
+protected:
+	void contextMenuEvent (QContextMenuEvent* ev);
+};
+
+// =============================================================================
+// ForgeWindow
+// 
+// The one main GUI class. Hosts the renderer, object list, message log. Contains
+// slot_action, which is what all actions connect to. Manages menus and toolbars.
+// Large and in charge.
+// =============================================================================
+class ForgeWindow : public QMainWindow {
+	Q_OBJECT
+	
+public:
+	ForgeWindow ();
+	void buildObjList ();
+	void setTitle ();
+	void refresh ();
+	ulong getInsertionPoint ();
+	void deleteSelection (vector<ulong>* ulapIndices, std::vector<LDObject*>* papObjects);
+	void updateToolBars ();
+	void updateRecentFilesMenu ();
+	void updateSelection ();
+	void updateGridToolBar ();
+	bool isSelected (LDObject* obj);
+	short getSelectedColor();
+	LDObject::Type uniformSelectedType ();
+	void scrollToSelection ();
+	void spawnContextMenu (const QPoint pos);
+	DelHistory* deleteObjVector (const std::vector<LDObject*> objs);
+	DelHistory* deleteSelection ();
+	DelHistory* deleteByColor (const short colnum);
+	GLRenderer* R () { return m_renderer; }
+	std::vector<LDObject*>& sel () { return m_sel; }
+	void setQuickColorMeta (std::vector<quickColorMetaEntry>& quickColorMeta) {
+		m_colorMeta = quickColorMeta;
+	}
+	
+protected:
+	void closeEvent (QCloseEvent* ev);
+	void logVA (LogType eType, const char* fmtstr, va_list va);
+	
+	friend void logf (const char* fmt, ...);
+	friend void logf (LogType eType, const char* fmt, ...);
+	friend void warnf (const char* fmt, ...);
+	friend void infof (const char* fmt, ...);
+	friend void succf (const char* fmt, ...);
+	friend void errf (const char* fmt, ...);
+	friend void devf (const char* fmt, ...);
+	
+private:
+	GLRenderer* m_renderer;
+	ObjectList* m_objList;
+	QMenu* m_recentFilesMenu;
+	QSplitter* m_splitter;
+	str m_msglogHTML;
+	QToolBar* m_colorToolBar;
+	std::vector<QToolBar*> m_toolBars;
+	std::vector<LDObject*> m_sel;
+	std::vector<quickColorMetaEntry> m_colorMeta;
+	std::vector<QPushButton*> m_colorButtons;
+	std::vector<QAction*> m_recentFiles;
+	
+	void createMenuActions ();
+	void createMenus ();
+	void createToolbars ();
+	void initSingleToolBar (const char* name);
+	void addToolBarAction (const char* name);
+	
+	QMenu* initMenu (const char* name);
+	void addMenuAction (const char* name);
+
+private slots:
+	void slot_selectionChanged ();
+	void slot_action ();
+	void slot_recentFile ();
+	void slot_quickColor ();
+	void slot_lastSecondCleanup ();
+};
+
+// =============================================================================
+// LabeledWidget
+//
+// Convenience class for a widget with a label beside it.
+// =============================================================================
+template<class R> class LabeledWidget : public QWidget {
+public:
+	explicit LabeledWidget (const char* labelstr, QWidget* parent = null) : QWidget (parent) {
+		m_widget = new R (this);
+		commonInit (labelstr);
+	}
+	
+	explicit LabeledWidget (const char* labelstr, R* widget, QWidget* parent = null) :
+		QWidget (parent), m_widget (widget)
+	{
+		commonInit (labelstr);
+	}
+	
+	explicit LabeledWidget (QWidget* parent = 0, Qt::WindowFlags f = 0) {
+		m_widget = new R (this);
+		commonInit ("");
+	}
+	
+	R* widget () const { return m_widget; }
+	R* w () const { return m_widget; }
+	QLabel* label () const { return m_label; }
+	QLabel* l () const { return m_label; }
+	void setWidget (R* widget) { m_widget = widget; }
+	void setLabel (QLabel* label) { m_label = label; }
+	operator R* () { return m_widget; }
+	
+private:
+	Q_DISABLE_COPY (LabeledWidget<R>)
+	
+	void commonInit (const char* labelstr) {
+		m_label = new QLabel (labelstr, this);
+		m_layout = new QHBoxLayout;
+		m_layout->addWidget (m_label);
+		m_layout->addWidget (m_widget);
+		setLayout (m_layout);
+	}
+	
+	R* m_widget;
+	QLabel* m_label;
+	QHBoxLayout* m_layout;
+};
+
+// -----------------------------------------------------------------------------
+// Other GUI-related stuff not directly part of ForgeWindow:
+QPixmap getIcon (const char* sIconName);
+std::vector<quickColorMetaEntry> parseQuickColorMeta ();
+bool confirm (str title, str msg);
+bool confirm (str msg);
+void critical (str msg);
+QAction* findAction (str name);
+void makeColorSelector (QComboBox* box);
+
+// -----------------------------------------------------------------------------
+// Pointer to the instance of ForgeWindow.
+extern ForgeWindow* g_win;
+
+#endif // GUI_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui_actions.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,419 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <qfiledialog.h>
+#include <qmessagebox.h>
+#include "gui.h"
+#include "file.h"
+#include "history.h"
+#include "newPartDialog.h"
+#include "configDialog.h"
+#include "addObjectDialog.h"
+#include "aboutDialog.h"
+#include "misc.h"
+#include "ldrawPathDialog.h"
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (newFile, "&New", "brick", "Create a new part model.", CTRL (N)) {
+	NewPartDialog::StaticDialog ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (open, "&Open", "file-open", "Load a part model from a file.", CTRL (O)) {
+	str zName;
+	zName += QFileDialog::getOpenFileName (g_win, "Open File",
+		"", "LDraw files (*.dat *.ldr)");
+	
+	if (~zName)
+		openMainFile (zName);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void doSave (bool saveAs) {
+	str path = g_curfile->m_filename;
+	
+	if (~path == 0 || saveAs) {
+		path = QFileDialog::getSaveFileName (g_win, "Save As",
+		"", "LDraw files (*.dat *.ldr)");
+		
+		if (~path == 0) {
+			// User didn't give a file name. This happens if the user cancelled
+			// saving in the save file dialog. Abort.
+			return;
+		}
+	}
+	
+	if (g_curfile->save (path)) {
+		g_curfile->m_filename = path;
+		g_win->setTitle ();
+		
+		logf ("Saved successfully to %s\n", path.chars ());
+	} else {
+		setlocale (LC_ALL, "C");
+		
+		// Tell the user the save failed, and give the option for saving as with it.
+		QMessageBox dlg (QMessageBox::Critical, "Save Failure",
+			fmt ("Failed to save to %s\nReason: %s", path.chars(), strerror (g_curfile->lastError)),
+			QMessageBox::Close, g_win);
+		
+		QPushButton* saveAsBtn = new QPushButton ("Save As");
+		saveAsBtn->setIcon (getIcon ("file-save-as"));
+		dlg.addButton (saveAsBtn, QMessageBox::ActionRole);
+		dlg.setDefaultButton (QMessageBox::Close);
+		dlg.exec ();
+		
+		if (dlg.clickedButton () == saveAsBtn)
+			doSave (true); // yay recursion!
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (save, "&Save", "file-save", "Save the part model.", CTRL (S)) {
+	doSave (false);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (saveAs, "Save &As", "file-save-as", "Save the part model to a specific file.", CTRL_SHIFT (S)) {
+	doSave (true);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (settings, "Settin&gs", "settings", "Edit the settings of " APPNAME ".", (0)) {
+	ConfigDialog::staticDialog ();
+}
+
+MAKE_ACTION (setLDrawPath, "Set LDraw Path", "settings", "Change the LDraw directory path.", (0)) {
+	LDrawPathDialog dlg (true);
+	dlg.exec ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (exit, "&Exit", "exit", "Close " APPNAME ".", CTRL (Q)) {
+	exit (0);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (newSubfile, "New Subfile", "add-subfile", "Creates a new subfile reference.", 0) {
+	AddObjectDialog::staticDialog (LDObject::Subfile, null);
+}
+
+MAKE_ACTION (newLine, "New Line",  "add-line", "Creates a new line.", 0) {
+	AddObjectDialog::staticDialog (LDObject::Line, null);
+}
+
+MAKE_ACTION (newTriangle, "New Triangle", "add-triangle", "Creates a new triangle.", 0) {
+	AddObjectDialog::staticDialog (LDObject::Triangle, null);
+}
+
+MAKE_ACTION (newQuad, "New Quadrilateral", "add-quad", "Creates a new quadrilateral.", 0) {
+	AddObjectDialog::staticDialog (LDObject::Quad, null);
+}
+
+MAKE_ACTION (newCondLine, "New Conditional Line", "add-condline", "Creates a new conditional line.", 0) {
+	AddObjectDialog::staticDialog (LDObject::CondLine, null);
+}
+
+MAKE_ACTION (newComment, "New Comment", "add-comment", "Creates a new comment.", 0) {
+	AddObjectDialog::staticDialog (LDObject::Comment, null);
+}
+
+MAKE_ACTION (newBFC, "New BFC Statement", "add-bfc", "Creates a new BFC statement.", 0) {
+	AddObjectDialog::staticDialog (LDObject::BFC, null);
+}
+
+MAKE_ACTION (newVertex, "New Vertex", "add-vertex", "Creates a new vertex.", 0) {
+	AddObjectDialog::staticDialog (LDObject::Vertex, null);
+}
+
+MAKE_ACTION (newRadial, "New Radial", "add-radial", "Creates a new radial.", 0) {
+	AddObjectDialog::staticDialog (LDObject::Radial, null);
+}
+
+MAKE_ACTION (editObject, "Edit Object", "edit-object", "Edits this object.", 0) {
+	if (g_win->sel ().size () != 1)
+		return;
+	
+	LDObject* obj = g_win->sel ()[0];
+	AddObjectDialog::staticDialog (obj->getType (), obj);
+}
+
+MAKE_ACTION (help, "Help", "help", "Shows the " APPNAME " help manual.", KEY (F1)) {
+	
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (about, "About " APPNAME, "ldforge",
+	"Shows information about " APPNAME ".", (0))
+{
+	AboutDialog dlg;
+	dlg.exec ();
+}
+
+MAKE_ACTION (aboutQt, "About Qt", "qt", "Shows information about Qt.", (0)) {
+	QMessageBox::aboutQt (g_win);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (selectAll, "Select All", "select-all", "Selects all objects.", CTRL (A)) {
+	g_win->sel ().clear ();
+	
+	for (LDObject* obj : g_curfile->m_objs)
+		g_win->sel ().push_back (obj);
+	
+	g_win->updateSelection ();
+}
+
+// =============================================================================
+MAKE_ACTION (selectByColor, "Select by Color", "select-color",
+	"Select all objects by the given color.", CTRL_SHIFT (A))
+{
+	short dColor = g_win->getSelectedColor ();
+	
+	if (dColor == -1)
+		return; // no consensus on color
+	
+	g_win->sel ().clear ();
+	for (LDObject* obj : g_curfile->m_objs)
+		if (obj->dColor == dColor)
+			g_win->sel ().push_back (obj);
+	
+	g_win->updateSelection ();
+}
+
+// =============================================================================
+MAKE_ACTION (selectByType, "Select by Type", "select-type",
+	"Select all objects by the given type.", (0))
+{
+	if (g_win->sel ().size () == 0)
+		return;
+	
+	LDObject::Type eType = g_win->uniformSelectedType ();
+	
+	if (eType == LDObject::Unidentified)
+		return;
+	
+	// If we're selecting subfile references, the reference filename must also
+	// be uniform.
+	str zRefName;
+	
+	if (eType == LDObject::Subfile) {
+		zRefName = static_cast<LDSubfile*> (g_win->sel ()[0])->zFileName;
+		
+		for (LDObject* pObj : g_win->sel ())
+			if (static_cast<LDSubfile*> (pObj)->zFileName != zRefName)
+				return;
+	}
+	
+	g_win->sel ().clear ();
+	for (LDObject* obj : g_curfile->m_objs) {
+		if (obj->getType() != eType)
+			continue;
+		
+		if (eType == LDObject::Subfile && static_cast<LDSubfile*> (obj)->zFileName != zRefName)
+			continue;
+		
+		g_win->sel ().push_back (obj);
+	}
+	
+	g_win->updateSelection ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (gridCoarse, "Coarse Grid", "grid-coarse", "Set the grid to Coarse", CTRL (1)) {
+	grid = Grid::Coarse;
+	g_win->updateGridToolBar ();
+}
+
+MAKE_ACTION (gridMedium, "Medium Grid", "grid-medium", "Set the grid to Medium", CTRL (2)) {
+	grid = Grid::Medium;
+	g_win->updateGridToolBar ();
+}
+
+MAKE_ACTION (gridFine, "Fine Grid", "grid-fine", "Set the grid to Fine", CTRL (3)) {
+	grid = Grid::Fine;
+	g_win->updateGridToolBar ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (resetView, "Reset View", "reset-view", "Reset view angles, pan and zoom", CTRL (0)) {
+	g_win->R ()->resetAngles ();
+	g_win->R ()->update ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (insertFrom, "Insert from File", "insert-from", "Insert LDraw data from a file.", (0)) {
+	str fname = QFileDialog::getOpenFileName ();
+	ulong idx = g_win->getInsertionPoint ();
+	
+	if (!~fname)
+		return;
+	
+	FILE* fp = fopen (fname, "r");
+	if (!fp) {
+		critical (fmt ("Couldn't open %s\n%s", fname.chars(), strerror (errno)));
+		return;
+	}
+	
+	std::vector<LDObject*> historyCopies;
+	std::vector<ulong> historyIndices;
+	std::vector<LDObject*> objs = loadFileContents (fp, null);
+	
+	g_win->sel ().clear ();
+	
+	for (LDObject* obj : objs) {
+		historyCopies.push_back (obj->clone ());
+		historyIndices.push_back (idx);
+		g_curfile->insertObj (idx, obj);
+		g_win->sel ().push_back (obj);
+		
+		idx++;
+	}
+	
+	if (historyCopies.size() > 0) {
+		History::addEntry (new AddHistory (historyIndices, historyCopies));
+		g_win->refresh ();
+		g_win->scrollToSelection ();
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (insertRaw, "Insert Raw", "insert-raw", "Type in LDraw code to insert.", (0)) {
+	ulong idx = g_win->getInsertionPoint ();
+	
+	QDialog* const dlg = new QDialog;
+	QVBoxLayout* const layout = new QVBoxLayout;
+	QTextEdit* const te_edit = new QTextEdit;
+	QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+	std::vector<LDObject*> historyCopies;
+	std::vector<ulong> historyIndices;
+	
+	layout->addWidget (te_edit);
+	layout->addWidget (bbx_buttons);
+	dlg->setLayout (layout);
+	dlg->setWindowTitle (APPNAME ": Insert Raw");
+	dlg->connect (bbx_buttons, SIGNAL (accepted ()), dlg, SLOT (accept ()));
+	dlg->connect (bbx_buttons, SIGNAL (rejected ()), dlg, SLOT (reject ()));
+	
+	if (dlg->exec () == false)
+		return;
+	
+	g_win->sel ().clear ();
+	
+	for (str line : str (te_edit->toPlainText ()).split ("\n")) {
+		LDObject* obj = parseLine (line);
+		
+		g_curfile->insertObj (idx, obj);
+		historyIndices.push_back (idx);
+		historyCopies.push_back (obj->clone ());
+		g_win->sel ().push_back (obj);
+		idx++;
+	}
+	
+	if (historyCopies.size () > 0) {
+		History::addEntry (new AddHistory (historyIndices, historyCopies));
+		g_win->refresh ();
+		g_win->scrollToSelection ();
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (screencap, "Screencap Part", "screencap", "Save a picture of the model", (0)) {
+	setlocale (LC_ALL, "C");
+	
+	ushort w, h;
+	uchar* imagedata = g_win->R ()->screencap (w, h);
+	
+	// GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well.
+	QImage img = QImage (imagedata, w, h, QImage::Format_ARGB32).rgbSwapped ().mirrored ();
+	
+	str root = basename (g_curfile->m_filename.chars ());
+	if (root.substr (~root - 4, -1) == ".dat")
+		root -= 4;
+	
+	str defaultname = (~root > 0) ? fmt ("%s.png", root.chars ()) : "";
+	str fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname,
+		"PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)");
+	
+	if (~fname > 0 && !img.save (fname))
+		critical (fmt ("Couldn't open %s for writing to save screencap: %s", fname.chars(), strerror (errno)));
+	
+	delete[] imagedata;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+extern_cfg (bool, gl_axes);
+MAKE_ACTION (axes, "Draw Axes", "axes", "Toggles drawing of axes", (0)) {
+	gl_axes = !gl_axes;
+	ACTION (axes)->setChecked (gl_axes);
+	g_win->R ()->update ();
+}
+
+// =============================================================================
+MAKE_ACTION (beginDraw, "Begin Drawing", "draw", "Begin drawing geometry", KEY (Insert)) {
+	g_win->R ()->beginPlaneDraw ();
+}
+
+MAKE_ACTION (cancelDraw, "Cancel Drawing", "draw-cancel", "Cancel drawing geometry", KEY (Escape)) {
+	g_win->R ()->endPlaneDraw (false);
+}
+
+MAKE_ACTION (doneDraw, "Done Drawing", "draw-done", "Done drawing geometry", KEY (Enter)) {
+	g_win->R ()->endPlaneDraw (true);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (visibility, "Toggle Visibility", "visibility", "Toggles visibility/hiding on objects.", (0)) {
+	for (LDObject* obj : g_win->sel ())
+		obj->setHidden (!obj->hidden ());
+	
+	g_win->refresh ();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui_editactions.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,687 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui.h"
+#include "common.h"
+#include "file.h"
+#include "history.h"
+#include "colorSelectDialog.h"
+#include "historyDialog.h"
+#include "setContentsDialog.h"
+#include "misc.h"
+#include "bbox.h"
+#include "radiobox.h"
+#include "extprogs.h"
+#include <qspinbox.h>
+#include <qcheckbox.h>
+
+vector<LDObject*> g_Clipboard;
+
+cfg (bool, edit_schemanticinline, false);
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static bool copyToClipboard () {
+	vector<LDObject*> objs = g_win->sel ();
+	
+	if (objs.size() == 0)
+		return false;
+	
+	// Clear the clipboard first.
+	for (LDObject* obj : g_Clipboard)
+		delete obj;
+	
+	g_Clipboard.clear ();
+	
+	// Now, copy the contents into the clipboard. The objects should be
+	// separate objects so that modifying the existing ones does not affect
+	// the clipboard. Thus, we add clones of the objects to the clipboard, not
+	// the objects themselves.
+	for (ulong i = 0; i < objs.size(); ++i)
+		g_Clipboard.push_back (objs[i]->clone ());
+	
+	return true;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (cut, "Cut", "cut", "Cut the current selection to clipboard.", CTRL (X)) {
+	vector<ulong> ulaIndices;
+	vector<LDObject*> copies;
+	
+	if (!copyToClipboard ())
+		return;
+	
+	g_win->deleteSelection (&ulaIndices, &copies);
+	History::addEntry (new DelHistory (ulaIndices, copies, DelHistory::Cut));
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (copy, "Copy", "copy", "Copy the current selection to clipboard.", CTRL (C)) {
+	copyToClipboard ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (paste, "Paste", "paste", "Paste clipboard contents.", CTRL (V)) {
+	vector<ulong> historyIndices;
+	vector<LDObject*> historyCopies;
+	
+	ulong idx = g_win->getInsertionPoint ();
+	g_win->sel ().clear ();
+	
+	for (LDObject* obj : g_Clipboard) {
+		historyIndices.push_back (idx);
+		historyCopies.push_back (obj->clone ());
+		
+		LDObject* copy = obj->clone ();
+		g_curfile->insertObj (idx, copy);
+		g_win->sel ().push_back (copy);
+	}
+	
+	History::addEntry (new AddHistory (historyIndices, historyCopies, AddHistory::Paste));
+	g_win->refresh ();
+	g_win->scrollToSelection ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (del, "Delete", "delete", "Delete the selection", KEY (Delete)) {
+	vector<ulong> ulaIndices;
+	vector<LDObject*> copies;
+	
+	g_win->deleteSelection (&ulaIndices, &copies);
+	
+	if (copies.size ())
+		History::addEntry (new DelHistory (ulaIndices, copies));
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static void doInline (bool bDeep) {
+	vector<LDObject*> sel = g_win->sel ();
+	
+	// History stuff
+	vector<LDSubfile*> paRefs;
+	vector<ulong> ulaRefIndices, ulaBitIndices;
+	
+	for (LDObject* obj : sel) {
+		if (obj->getType() != LDObject::Subfile)
+			continue;
+		
+		ulaRefIndices.push_back (obj->getIndex (g_curfile));
+		paRefs.push_back (static_cast<LDSubfile*> (obj)->clone ());
+	}
+	
+	for (LDObject* obj : sel) {
+		// Get the index of the subfile so we know where to insert the
+		// inlined contents.
+		long idx = obj->getIndex (g_curfile);
+		if (idx == -1)
+			continue;
+		
+		vector<LDObject*> objs;
+		
+		if (obj->getType() == LDObject::Subfile)
+			objs = static_cast<LDSubfile*> (obj)->inlineContents (bDeep, true);
+		else if (obj->getType() == LDObject::Radial)
+			objs = static_cast<LDRadial*> (obj)->decompose (true);
+		else
+			continue;
+		
+		// Merge in the inlined objects
+		for (LDObject* inlineobj : objs) {
+			ulaBitIndices.push_back (idx);
+			
+			// This object is now inlined so it has no parent anymore.
+			inlineobj->parent = null;
+			
+			g_curfile->insertObj (idx++, inlineobj);
+		}
+		
+		// Delete the subfile now as it's been inlined.
+		g_curfile->forgetObject (obj);
+		delete obj;
+	}
+	
+	History::addEntry (new InlineHistory (ulaBitIndices, ulaRefIndices, paRefs, bDeep));
+	g_win->refresh ();
+}
+
+MAKE_ACTION (inlineContents, "Inline", "inline", "Inline selected subfiles.", CTRL (I)) {
+	doInline (false);
+}
+
+MAKE_ACTION (deepInline, "Deep Inline", "inline-deep", "Recursively inline selected subfiles "
+	"down to polygons only.", CTRL_SHIFT (I))
+{
+	doInline (true);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (splitQuads, "Split Quads", "quad-split", "Split quads into triangles.", (0)) {
+	vector<LDObject*> objs = g_win->sel ();
+	
+	vector<ulong> ulaIndices;
+	vector<LDQuad*> paCopies;
+	
+	// Store stuff first for history archival
+	for (LDObject* obj : objs) {
+		if (obj->getType() != LDObject::Quad)
+			continue;
+		
+		ulaIndices.push_back (obj->getIndex (g_curfile));
+		paCopies.push_back (static_cast<LDQuad*> (obj)->clone ());
+	}
+	
+	for (LDObject* obj : objs) {
+		if (obj->getType() != LDObject::Quad)
+			continue;
+		
+		// Find the index of this quad
+		long lIndex = obj->getIndex (g_curfile);
+		
+		if (lIndex == -1)
+			return;
+		
+		std::vector<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles ();
+		
+		// Replace the quad with the first triangle and add the second triangle
+		// after the first one.
+		g_curfile->m_objs[lIndex] = triangles[0];
+		g_curfile->insertObj (lIndex + 1, triangles[1]);
+		
+		// Delete this quad now, it has been split.
+		delete obj;
+	}
+	
+	History::addEntry (new QuadSplitHistory (ulaIndices, paCopies));
+	g_win->refresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (setContents, "Set Contents", "set-contents", "Set the raw code of this object.", KEY (F9)) {
+	if (g_win->sel ().size() != 1)
+		return;
+	
+	LDObject* obj = g_win->sel ()[0];
+	SetContentsDialog::staticDialog (obj);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (setColor, "Set Color", "palette", "Set the color on given objects.", KEY (F10)) {
+	if (g_win->sel ().size() <= 0)
+		return;
+	
+	short dColor;
+	short dDefault = -1;
+	
+	std::vector<LDObject*> objs = g_win->sel ();
+	
+	// If all selected objects have the same color, said color is our default
+	// value to the color selection dialog.
+	dDefault = g_win->getSelectedColor ();
+	
+	// Show the dialog to the user now and ask for a color.
+	if (ColorSelectDialog::staticDialog (dColor, dDefault, g_win)) {
+		std::vector<ulong> ulaIndices;
+		std::vector<short> daColors;
+		
+		for (LDObject* obj : objs) {
+			if (obj->dColor != -1) {
+				ulaIndices.push_back (obj->getIndex (g_curfile));
+				daColors.push_back (obj->dColor);
+				
+				obj->dColor = dColor;
+			}
+		}
+		
+		History::addEntry (new SetColorHistory (ulaIndices, daColors, dColor));
+		g_win->refresh ();
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (makeBorders, "Make Borders", "make-borders", "Add borders around given polygons.",
+	CTRL_SHIFT (B))
+{
+	vector<LDObject*> objs = g_win->sel ();
+	
+	vector<ulong> ulaIndices;
+	vector<LDObject*> paObjs;
+	
+	for (LDObject* obj : objs) {
+		if (obj->getType() != LDObject::Quad && obj->getType() != LDObject::Triangle)
+			continue;
+		
+		short dNumLines;
+		LDLine* lines[4];
+		
+		if (obj->getType() == LDObject::Quad) {
+			dNumLines = 4;
+			
+			LDQuad* quad = static_cast<LDQuad*> (obj);
+			lines[0] = new LDLine (quad->vaCoords[0], quad->vaCoords[1]);
+			lines[1] = new LDLine (quad->vaCoords[1], quad->vaCoords[2]);
+			lines[2] = new LDLine (quad->vaCoords[2], quad->vaCoords[3]);
+			lines[3] = new LDLine (quad->vaCoords[3], quad->vaCoords[0]);
+		} else {
+			dNumLines = 3;
+			
+			LDTriangle* tri = static_cast<LDTriangle*> (obj);
+			lines[0] = new LDLine (tri->vaCoords[0], tri->vaCoords[1]);
+			lines[1] = new LDLine (tri->vaCoords[1], tri->vaCoords[2]);
+			lines[2] = new LDLine (tri->vaCoords[2], tri->vaCoords[0]); 
+		}
+		
+		for (short i = 0; i < dNumLines; ++i) {
+			ulong idx = obj->getIndex (g_curfile) + i + 1;
+			
+			lines[i]->dColor = edgecolor;
+			g_curfile->insertObj (idx, lines[i]);
+			
+			ulaIndices.push_back (idx);
+			paObjs.push_back (lines[i]->clone ());
+		}
+	}
+	
+	History::addEntry (new AddHistory (ulaIndices, paObjs));
+	g_win->refresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (makeCornerVerts, "Make Corner Vertices", "corner-verts",
+	"Adds vertex objects to the corners of the given polygons", (0))
+{
+	vector<ulong> ulaIndices;
+	vector<LDObject*> paObjs;
+	
+	for (LDObject* obj : g_win->sel ()) {
+		if (obj->vertices () < 2)
+			continue;
+		
+		ulong idx = obj->getIndex (g_curfile);
+		for (short i = 0; i < obj->vertices(); ++i) {
+			LDVertex* vert = new LDVertex;
+			vert->vPosition = obj->vaCoords[i];
+			vert->dColor = obj->dColor;
+			
+			g_curfile->insertObj (++idx, vert);
+			ulaIndices.push_back (idx);
+			paObjs.push_back (vert->clone ());
+		}
+	}
+	
+	if (ulaIndices.size() > 0) {
+		History::addEntry (new AddHistory (ulaIndices, paObjs));
+		g_win->refresh ();
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static void doMoveSelection (const bool bUp) {
+	vector<LDObject*> objs = g_win->sel ();
+	
+	// Get the indices of the objects for history archival
+	vector<ulong> ulaIndices;
+	for (LDObject* obj : objs)
+		ulaIndices.push_back (obj->getIndex (g_curfile));
+	
+	LDObject::moveObjects (objs, bUp);
+	History::addEntry (new ListMoveHistory (ulaIndices, bUp));
+	g_win->buildObjList ();
+}
+
+MAKE_ACTION (moveUp, "Move Up", "arrow-up", "Move the current selection up.", SHIFT (Up)) {
+	doMoveSelection (true);
+}
+
+MAKE_ACTION (moveDown, "Move Down", "arrow-down", "Move the current selection down.", SHIFT (Down)) {
+	doMoveSelection (false);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (undo, "Undo", "undo", "Undo a step.", CTRL (Z)) {
+	History::undo ();
+}
+
+MAKE_ACTION (redo, "Redo", "redo", "Redo a step.", CTRL_SHIFT (Z)) {
+	History::redo ();
+}
+
+MAKE_ACTION (showHistory, "Edit History", "history", "Show the history dialog.", (0)) {
+	HistoryDialog dlg;
+	dlg.exec ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void doMoveObjects (vertex vVector) {
+	vector<ulong> ulaIndices;
+	
+	// Apply the grid values
+	vVector[X] *= currentGrid ().confs[Grid::X]->value;
+	vVector[Y] *= currentGrid ().confs[Grid::Y]->value;
+	vVector[Z] *= currentGrid ().confs[Grid::Z]->value;
+	
+	for (LDObject* obj : g_win->sel ()) {
+		ulaIndices.push_back (obj->getIndex (g_curfile));
+		obj->move (vVector);
+	}
+	
+	History::addEntry (new MoveHistory (ulaIndices, vVector));
+	g_win->refresh ();
+}
+
+MAKE_ACTION (moveXNeg, "Move -X", "move-x-neg", "Move selected objects negative on the X axis.", KEY (Left)) {
+	doMoveObjects ({-1, 0, 0});
+}
+
+MAKE_ACTION (moveYNeg, "Move -Y", "move-y-neg", "Move selected objects negative on the Y axis.", KEY (PageUp)) {
+	doMoveObjects ({0, -1, 0});
+}
+
+MAKE_ACTION (moveZNeg, "Move -Z", "move-z-neg", "Move selected objects negative on the Z axis.", KEY (Down)) {
+	doMoveObjects ({0, 0, -1});
+}
+
+MAKE_ACTION (moveXPos, "Move +X", "move-x-pos", "Move selected objects positive on the X axis.", KEY (Right)) {
+	doMoveObjects ({1, 0, 0});
+}
+
+MAKE_ACTION (moveYPos, "Move +Y", "move-y-pos", "Move selected objects positive on the Y axis.", KEY (PageDown)) {
+	doMoveObjects ({0, 1, 0});
+}
+
+MAKE_ACTION (moveZPos, "Move +Z", "move-z-pos", "Move selected objects positive on the Z axis.", KEY (Up)) {
+	doMoveObjects ({0, 0, 1});
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// invert - reverse winding
+// 
+// NOTE: History management gets a little tricky here. For lines, cond-lines,
+// triangles and quads, we edit the object, thus we record an EditHistory. For
+// subfiles and radials we create or delete invertnext objects. Since we have
+// multiple actions of different types, we store a history entry for each of
+// them and wrap them into a ComboHistory, which allows storage of multiple
+// simultaneous edits with different type. This is what we ultimately store into
+// History.
+// =============================================================================
+MAKE_ACTION (invert, "Invert", "invert", "Reverse the winding of given objects.", CTRL_SHIFT (W)) {
+	std::vector<LDObject*> paSelection = g_win->sel ();
+	std::vector<HistoryEntry*> paHistory;
+	
+	for (LDObject* obj : paSelection) {
+		// For the objects we end up editing, we store information into these
+		// variables and we store them into an EditHistory after the switch
+		// block. Subfile and radial management is stored into the history
+		// list immediately.
+		ulong ulHistoryIndex = obj->getIndex (g_curfile);
+		LDObject* pOldCopy, *pNewCopy;
+		bool bEdited = false;
+		
+		switch (obj->getType ()) {
+		case LDObject::Line:
+		case LDObject::CondLine:
+			{
+				// For lines, we swap the vertices. I don't think that a
+				// cond-line's control points need to be swapped, do they?
+				LDLine* pLine = static_cast<LDLine*> (obj);
+				vertex vTemp = pLine->vaCoords[0];
+				
+				pOldCopy = pLine->clone ();
+				pLine->vaCoords[0] = pLine->vaCoords[1];
+				pLine->vaCoords[1] = vTemp;
+				pNewCopy = pLine->clone ();
+				bEdited = true;
+			}
+			break;
+		
+		case LDObject::Triangle:
+			{
+				// Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1.
+				// Thus, we swap 1 and 2.
+				LDTriangle* pTri = static_cast<LDTriangle*> (obj);
+				vertex vTemp = pTri->vaCoords[1];
+				
+				pOldCopy = pTri->clone ();
+				pTri->vaCoords[1] = pTri->vaCoords[2];
+				pTri->vaCoords[2] = vTemp;
+				pNewCopy = pTri->clone ();
+				bEdited = true;
+			}
+			break;
+		
+		case LDObject::Quad:
+			{
+				// Quad: 0 -> 1 -> 2 -> 3
+				// rev:  0 -> 3 -> 2 -> 1
+				// Thus, we swap 1 and 3.
+				LDQuad* pQuad = static_cast<LDQuad*> (obj);
+				vertex vTemp = pQuad->vaCoords[1];
+				
+				pOldCopy = pQuad->clone ();
+				pQuad->vaCoords[1] = pQuad->vaCoords[3];
+				pQuad->vaCoords[3] = vTemp;
+				pNewCopy = pQuad->clone ();
+				bEdited = true;
+			}
+			break;
+		
+		case LDObject::Subfile:
+		case LDObject::Radial:
+			{
+				// Subfiles and radials are inverted when they're prefixed with
+				// a BFC INVERTNEXT statement. Thus we need to toggle this status.
+				// For flat primitives it's sufficient that the determinant is
+				// flipped but I don't have a method for checking flatness yet.
+				// Food for thought...
+				
+				bool inverted = false;
+				ulong idx = obj->getIndex (g_curfile);
+				
+				if (idx > 0) {
+					LDObject* prev = g_curfile->object (idx - 1);
+					LDBFC* bfc = dynamic_cast<LDBFC*> (prev);
+					
+					if (bfc && bfc->type == LDBFC::InvertNext) {
+						// Object is prefixed with an invertnext, thus remove it.
+						paHistory.push_back (new DelHistory ({idx - 1}, {bfc->clone ()}));
+						
+						inverted = true;
+						g_curfile->forgetObject (bfc);
+						delete bfc;
+					}
+				}
+				
+				if (!inverted) {
+					// Not inverted, thus prefix it with a new invertnext.
+					LDBFC* bfc = new LDBFC (LDBFC::InvertNext);
+					g_curfile->insertObj (idx, bfc);
+					
+					paHistory.push_back (new AddHistory ({idx}, {bfc->clone ()}));
+				}
+			}
+			break;
+		
+		default:
+			break;
+		}
+		
+		// If we edited this object, store the EditHistory based on collected
+		// information now.
+		if (bEdited == true)
+			paHistory.push_back (new EditHistory ({ulHistoryIndex}, {pOldCopy}, {pNewCopy}));
+	}
+	
+	if (paHistory.size () > 0) {
+		History::addEntry (new ComboHistory (paHistory));
+		g_win->refresh ();
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static void doRotate (const short l, const short m, const short n) {
+	std::vector<LDObject*> sel = g_win->sel ();
+	bbox box;
+	vertex origin;
+	std::vector<vertex*> queue;
+	const double angle = (pi * currentGrid ().confs[Grid::Angle]->value) / 360;
+	
+	// ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2
+	matrix<3> transform ({
+		(l * l * (1 - cos (angle))) + cos (angle),
+		(m * l * (1 - cos (angle))) - (n * sin (angle)),
+		(n * l * (1 - cos (angle))) + (m * sin (angle)),
+		
+		(l * m * (1 - cos (angle))) + (n * sin (angle)),
+		(m * m * (1 - cos (angle))) + cos (angle),
+		(n * m * (1 - cos (angle))) - (l * sin (angle)),
+		
+		(l * n * (1 - cos (angle))) - (m * sin (angle)),
+		(m * n * (1 - cos (angle))) + (l * sin (angle)),
+		(n * n * (1 - cos (angle))) + cos (angle)
+	});
+	
+	// Calculate center vertex
+	for (LDObject* obj : sel) {
+		if (obj->getType () == LDObject::Subfile)
+			box << static_cast<LDSubfile*> (obj)->vPosition;
+		else if (obj->getType () == LDObject::Radial)
+			box << static_cast<LDRadial*> (obj)->vPosition;
+		else
+			box << obj;
+	}
+	
+	origin = box.center ();
+	
+	// Apply the above matrix to everything
+	for (LDObject* obj : sel) {
+		if (obj->vertices ())
+			for (short i = 0; i < obj->vertices (); ++i)
+				queue.push_back (&obj->vaCoords[i]);
+		else if (obj->getType () == LDObject::Subfile) {
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			
+			queue.push_back (&ref->vPosition);
+			ref->mMatrix = ref->mMatrix * transform;
+		} else if (obj->getType () == LDObject::Radial) {
+			LDRadial* rad = static_cast<LDRadial*> (obj);
+			
+			queue.push_back (&rad->vPosition);
+			rad->mMatrix = rad->mMatrix * transform;
+		} else if (obj->getType () == LDObject::Vertex)
+			queue.push_back (&static_cast<LDVertex*> (obj)->vPosition);
+	}
+	
+	for (vertex* v : queue) {
+		v->move (-origin);
+		v->transform (transform, g_origin);
+		v->move (origin);
+	}
+	
+	g_win->refresh ();
+}
+
+MAKE_ACTION (rotateXPos, "Rotate +X", "rotate-x-pos", "Rotate objects around X axis", CTRL (Right)) {
+	doRotate (1, 0, 0);
+}
+
+MAKE_ACTION (rotateYPos, "Rotate +Y", "rotate-y-pos", "Rotate objects around Y axis", CTRL (PageDown)) {
+	doRotate (0, 1, 0);
+}
+
+MAKE_ACTION (rotateZPos, "Rotate +Z", "rotate-z-pos", "Rotate objects around Z axis", CTRL (Up)) {
+	doRotate (0, 0, 1);
+}
+
+MAKE_ACTION (rotateXNeg, "Rotate -X", "rotate-x-neg", "Rotate objects around X axis", CTRL (Left)) {
+	doRotate (-1, 0, 0);
+}
+
+MAKE_ACTION (rotateYNeg, "Rotate -Y", "rotate-y-neg", "Rotate objects around Y axis", CTRL (PageUp)) {
+	doRotate (0, -1, 0);
+}
+
+MAKE_ACTION (rotateZNeg, "Rotate -Z", "rotate-z-neg", "Rotate objects around Z axis", CTRL (Down)) {
+	doRotate (0, 0, -1);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (roundCoords, "Round Coordinates", "round-coords", "Round coordinates down to 3/4 decimals", (0)) {
+	setlocale (LC_ALL, "C");
+	
+	for (LDObject* obj : g_win->sel ())
+	for (short i = 0; i < obj->vertices (); ++i)
+	for (const Axis ax : g_Axes)
+		obj->vaCoords[i][ax] = atof (fmt ("%.3f", obj->vaCoords[i][ax]));
+	
+	g_win->refresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MAKE_ACTION (uncolorize, "Uncolorize", "uncolorize", "Reduce colors of everything selected to main and edge colors", (0)) {
+	vector<LDObject*> oldCopies, newCopies;
+	vector<ulong> indices;
+	
+	for (LDObject* obj : g_win->sel ()) {
+		if (obj->isColored () == false)
+			continue;
+		
+		indices.push_back (obj->getIndex (g_curfile));
+		oldCopies.push_back (obj->clone ());
+		
+		obj->dColor = (obj->getType () == LDObject::Line || obj->getType () == LDObject::CondLine) ? edgecolor : maincolor;
+		newCopies.push_back (obj->clone ());
+	}
+	
+	if (indices.size () > 0) {
+		History::addEntry (new EditHistory (indices, oldCopies, newCopies));
+		g_win->refresh ();
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/history.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,351 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "history.h"
+#include "ldtypes.h"
+#include "file.h"
+#include "misc.h"
+#include "gui.h"
+
+EXTERN_ACTION (undo)
+EXTERN_ACTION (redo)
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+namespace History {
+	std::vector<HistoryEntry*> s_entries;
+	static long s_pos = -1;
+	
+	// =========================================================================
+	void addEntry (HistoryEntry* entry) {
+		// If there's any entries ahead the current position, we need to
+		// remove them now
+		for (ulong i = s_pos + 1; i < s_entries.size(); ++i) {
+			delete s_entries[i];
+			s_entries.erase (s_entries.begin() + i);
+		}
+		
+		s_entries.push_back (entry);
+		s_pos++;
+		
+		updateActions ();
+	}
+	
+	// =========================================================================
+	void undo () {
+		if (s_pos == -1)
+			return; // nothing to undo
+		
+		s_entries[s_pos--]->undo ();
+		updateActions ();
+	}
+	
+	// =========================================================================
+	void redo () {
+		if (s_pos == (long) s_entries.size () - 1)
+			return; // nothing to redo;
+		
+		s_entries[++s_pos]->redo ();
+		updateActions ();
+	}
+	
+	// =========================================================================
+	void clear () {
+		for (HistoryEntry* entry : s_entries)
+			delete entry;
+		
+		s_entries.clear ();
+		s_pos = -1;
+		updateActions ();
+	}
+	
+	// =========================================================================
+	void updateActions () {
+		ACTION (undo)->setEnabled (s_pos > -1);
+		ACTION (redo)->setEnabled (s_pos < (long) s_entries.size () - 1);
+		
+		// Update the window title as well
+		g_win->setTitle ();
+	}
+	
+	// =========================================================================
+	long pos () {
+		return s_pos;
+	}
+	
+	// =========================================================================
+	std::vector<HistoryEntry*>& entries () {
+		return s_entries;
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void DelHistory::undo () {
+	for (ulong i = 0; i < cache.size(); ++i) {
+		ulong idx = cache.size() - i - 1;
+		LDObject* obj = cache[idx]->clone ();
+		g_curfile->insertObj (indices[idx], obj);
+	}
+	
+	g_win->refresh ();
+}
+
+// =============================================================================
+void DelHistory::redo () {
+	for (ulong i = 0; i < cache.size(); ++i) {
+		LDObject* obj = g_curfile->m_objs[indices[i]];
+		
+		g_curfile->forgetObject (obj);
+		delete obj;
+	}
+	
+	g_win->refresh ();
+}
+
+// =============================================================================
+DelHistory::~DelHistory () {
+	for (LDObject* obj : cache)
+		delete obj;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void SetColorHistory::undo () {
+	// Restore colors
+	for (ulong i = 0; i < ulaIndices.size (); ++i)
+		g_curfile->m_objs[ulaIndices[i]]->dColor = daColors[i];
+	
+	g_win->refresh ();
+}
+
+void SetColorHistory::redo () {
+	// Re-set post color
+	for (ulong i = 0; i < ulaIndices.size (); ++i)
+		g_curfile->m_objs[ulaIndices[i]]->dColor = dNewColor;
+	
+	g_win->refresh ();
+}
+
+SetColorHistory::~SetColorHistory () {}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void EditHistory::undo () {
+	for (ulong idx : ulaIndices)
+		g_curfile->object (idx)->replace (paOldObjs[idx]->clone ());
+	
+	g_win->refresh ();
+}
+
+void EditHistory::redo () {
+	for (ulong idx : ulaIndices)
+		g_curfile->object (idx)->replace (paNewObjs[idx]->clone ());
+	
+	g_win->refresh ();
+}
+
+EditHistory::~EditHistory () {
+	for (ulong idx : ulaIndices) {
+		delete paOldObjs[idx];
+		delete paNewObjs[idx];
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+std::vector<LDObject*> ListMoveHistory::getObjects (short ofs) {
+	std::vector<LDObject*> objs;
+	
+	for (ulong idx : ulaIndices)
+		objs.push_back (g_curfile->m_objs[idx + ofs]);
+	
+	return objs;
+}
+
+void ListMoveHistory::undo () {
+	std::vector<LDObject*> objs = getObjects (bUp ? -1 : 1);
+	LDObject::moveObjects (objs, !bUp);
+	g_win->buildObjList ();
+}
+
+void ListMoveHistory::redo () {
+	std::vector<LDObject*> objs = getObjects (0);
+	LDObject::moveObjects (objs, bUp);
+	g_win->buildObjList ();
+}
+
+ListMoveHistory::~ListMoveHistory() {}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+AddHistory::~AddHistory () {
+	for (LDObject* pObj : paObjs)
+		delete pObj;
+}
+
+void AddHistory::undo () {
+	for (ulong i = 0; i < paObjs.size(); ++i) {
+		ulong idx = ulaIndices[ulaIndices.size() - i - 1];
+		LDObject* obj = g_curfile->m_objs[idx];
+		
+		g_curfile->forgetObject (obj);
+		delete obj;
+	}
+	
+	g_win->refresh ();
+}
+
+void AddHistory::redo () {
+	for (ulong i = 0; i < paObjs.size(); ++i) {
+		ulong idx = ulaIndices[i];
+		LDObject* obj = paObjs[i]->clone ();
+		
+		g_curfile->insertObj (idx, obj);
+	}
+	
+	g_win->refresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+QuadSplitHistory::~QuadSplitHistory () {
+	for (LDQuad* pQuad : paQuads)
+		delete pQuad;
+}
+
+void QuadSplitHistory::undo () {
+	for (ulong i = 0; i < paQuads.size(); ++i) {
+		// The quad was replaced by the first triangle and a second one was
+		// added after it. Thus, we remove the second one here and replace
+		// the first with a copy of the quad.
+		ulong idx = ulaIndices[i];
+		
+		LDTriangle* tri1 = static_cast<LDTriangle*> (g_curfile->m_objs[idx]),
+			*tri2 = static_cast<LDTriangle*> (g_curfile->m_objs[idx + 1]);
+		LDQuad* pCopy = paQuads[i]->clone ();
+		
+		tri1->replace (pCopy);
+		g_curfile->forgetObject (tri2);
+		delete tri2;
+	}
+	
+	g_win->refresh ();
+}
+
+void QuadSplitHistory::redo () {
+	for (long i = paQuads.size() - 1; i >= 0; --i) {
+		ulong idx = ulaIndices[i];
+		
+		LDQuad* pQuad = static_cast<LDQuad*> (g_curfile->m_objs[idx]);
+		std::vector<LDTriangle*> paTriangles = pQuad->splitToTriangles ();
+		
+		g_curfile->m_objs[idx] = paTriangles[0];
+		g_curfile->insertObj (idx + 1, paTriangles[1]);
+		delete pQuad;
+	}
+	
+	g_win->refresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void InlineHistory::undo () {
+	for (long i = ulaBitIndices.size() - 1; i >= 0; --i) {
+		LDObject* obj = g_curfile->m_objs [ulaBitIndices[i]];
+		g_curfile->forgetObject (obj);
+		delete obj;
+	}
+	
+	for (ulong i = 0; i < ulaRefIndices.size(); ++i) {
+		LDSubfile* obj = paRefs[i]->clone ();
+		g_curfile->insertObj (ulaRefIndices[i], obj);
+	}
+	
+	g_win->refresh ();
+}
+
+void InlineHistory::redo () {
+	for (long i = ulaRefIndices.size() - 1; i >= 0; --i) {
+		ulong idx = ulaRefIndices[i];
+		
+		assert (g_curfile->object (idx)->getType () == LDObject::Subfile);
+		LDSubfile* ref = static_cast<LDSubfile*> (g_curfile->object (idx));
+		vector<LDObject*> objs = ref->inlineContents (bDeep, false);
+		
+		for (LDObject* obj : objs)
+			g_curfile->insertObj (idx++, obj);
+		
+		g_curfile->forgetObject (ref);
+		delete ref;
+	}
+	
+	g_win->refresh ();
+}
+
+InlineHistory::~InlineHistory () {
+	for (LDSubfile* ref : paRefs)
+		delete ref;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+MoveHistory::~MoveHistory () {}
+
+void MoveHistory::undo () {
+	const vertex vInverse = -vVector;
+	
+	for (ulong i : ulaIndices)
+		g_curfile->object (i)->move (vInverse);
+	g_win->refresh ();
+}
+
+void MoveHistory::redo () {
+	for (ulong i : ulaIndices)
+		g_curfile->object (i)->move (vVector);
+	g_win->refresh ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+ComboHistory::~ComboHistory () {
+	for (HistoryEntry* pEntry : paEntries)
+		delete pEntry;
+}
+
+void ComboHistory::undo () {
+	for (long i = paEntries.size() - 1; i >= 0; --i) {
+		HistoryEntry* pEntry = paEntries[i];
+		pEntry->undo ();
+	}
+}
+
+void ComboHistory::redo () {
+	for (HistoryEntry* pEntry : paEntries)
+		pEntry->redo ();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/history.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,215 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HISTORY_H
+#define HISTORY_H
+
+#include "common.h"
+#include "ldtypes.h"
+
+#define IMPLEMENT_HISTORY_TYPE(N) \
+	virtual ~N##History (); \
+	virtual void undo (); \
+	virtual void redo (); \
+	virtual HistoryType type () { return HISTORY_##N; }
+
+// =============================================================================
+enum HistoryType {
+	HISTORY_Del,
+	HISTORY_SetColor,
+	HISTORY_Edit,
+	HISTORY_ListMove,
+	HISTORY_Add,
+	HISTORY_QuadSplit,
+	HISTORY_Inline,
+	HISTORY_Move,
+	HISTORY_Combo,
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class HistoryEntry {
+public:
+	virtual void undo () {}
+	virtual void redo () {}
+	virtual ~HistoryEntry () {}
+	virtual HistoryType type () { return (HistoryType)(0); }
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class DelHistory : public HistoryEntry {
+public:
+	enum Type {
+		Cut,	// were deleted with a cut operation
+		Other,	// were deleted witout specific reason
+	};
+	
+	IMPLEMENT_HISTORY_TYPE (Del)
+	
+	vector<ulong> indices;
+	vector<LDObject*> cache;
+	const Type eType;
+	
+	DelHistory (vector<ulong> indices, vector<LDObject*> cache, const Type eType = Other) :
+		indices (indices), cache (cache), eType (eType) {}
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class SetColorHistory : public HistoryEntry {
+public:
+	IMPLEMENT_HISTORY_TYPE (SetColor)
+	
+	vector<ulong> ulaIndices;
+	vector<short> daColors;
+	short dNewColor;
+	
+	SetColorHistory (vector<ulong> ulaIndices, vector<short> daColors, short dNewColor) :
+		ulaIndices (ulaIndices), daColors (daColors), dNewColor (dNewColor) {}
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class EditHistory : public HistoryEntry {
+public:
+	IMPLEMENT_HISTORY_TYPE (Edit)
+	
+	const std::vector<ulong> ulaIndices;
+	const std::vector<LDObject*> paOldObjs, paNewObjs;
+	
+	EditHistory (std::vector<ulong> ulaIndices, std::vector<LDObject*> paOldObjs,
+		std::vector<LDObject*> paNewObjs) :
+		ulaIndices (ulaIndices), paOldObjs (paOldObjs), paNewObjs (paNewObjs) {}
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class ListMoveHistory : public HistoryEntry {
+public:
+	IMPLEMENT_HISTORY_TYPE (ListMove)
+	
+	std::vector<ulong> ulaIndices;
+	bool bUp;
+	
+	std::vector<LDObject*> getObjects (short ofs);
+	ListMoveHistory (vector<ulong> ulaIndices, const bool bUp) :
+		ulaIndices (ulaIndices), bUp (bUp) {}
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class AddHistory : public HistoryEntry {
+public:
+	enum Type {
+		Other,	// was "just added"
+		Paste,	// was added through a paste operation
+	};
+	
+	IMPLEMENT_HISTORY_TYPE (Add)
+	
+	std::vector<ulong> ulaIndices;
+	std::vector<LDObject*> paObjs;
+	const Type eType;
+	
+	AddHistory (std::vector<ulong> ulaIndices, std::vector<LDObject*> paObjs,
+		const Type eType = Other) :
+		ulaIndices (ulaIndices), paObjs (paObjs), eType (eType) {}
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class QuadSplitHistory : public HistoryEntry {
+public:
+	IMPLEMENT_HISTORY_TYPE (QuadSplit)
+	
+	std::vector<ulong> ulaIndices;
+	std::vector<LDQuad*> paQuads;
+	
+	QuadSplitHistory (std::vector<ulong> ulaIndices, std::vector<LDQuad*> paQuads) :
+		ulaIndices (ulaIndices), paQuads (paQuads) {}
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class InlineHistory : public HistoryEntry {
+public:
+	IMPLEMENT_HISTORY_TYPE (Inline)
+	
+	const std::vector<ulong> ulaBitIndices, ulaRefIndices;
+	const std::vector<LDSubfile*> paRefs;
+	const bool bDeep;
+	
+	InlineHistory (const std::vector<ulong> ulaBitIndices, const std::vector<ulong> ulaRefIndices,
+		const std::vector<LDSubfile*> paRefs, const bool bDeep) :
+		ulaBitIndices (ulaBitIndices), ulaRefIndices (ulaRefIndices), paRefs (paRefs), bDeep (bDeep) {}
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class MoveHistory : public HistoryEntry {
+public:
+	IMPLEMENT_HISTORY_TYPE (Move)
+	
+	const std::vector<ulong> ulaIndices;
+	const vertex vVector;
+	
+	MoveHistory (const std::vector<ulong> ulaIndices, const vertex vVector) :
+		ulaIndices (ulaIndices), vVector (vVector) {}
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+class ComboHistory : public HistoryEntry {
+public:
+	IMPLEMENT_HISTORY_TYPE (Combo)
+	
+	std::vector<HistoryEntry*> paEntries;
+	
+	ComboHistory (std::vector<HistoryEntry*> paEntries) : paEntries (paEntries) {}
+	
+	ComboHistory& operator<< (HistoryEntry* entry) {
+		paEntries.push_back (entry);
+		return *this;
+	}
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+namespace History {
+	void addEntry (HistoryEntry* entry);
+	void undo ();
+	void redo ();
+	void clear ();
+	void updateActions ();
+	long pos ();
+	std::vector<HistoryEntry*>& entries ();
+};
+
+#endif // HISTORY_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/historyDialog.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,283 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "historyDialog.h"
+#include "history.h"
+#include "colors.h"
+#include <qboxlayout.h>
+#include <qmessagebox.h>
+
+EXTERN_ACTION (undo);
+EXTERN_ACTION (redo);
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+HistoryDialog::HistoryDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) {
+	historyList = new QListWidget;
+	undoButton = new QPushButton ("Undo");
+	redoButton = new QPushButton ("Redo");
+	clearButton = new QPushButton ("Clear");
+	buttons = new QDialogButtonBox (QDialogButtonBox::Close);
+	
+	historyList->setAlternatingRowColors (true);
+	
+	undoButton->setIcon (getIcon ("undo"));
+	redoButton->setIcon (getIcon ("redo"));
+	
+	connect (undoButton, SIGNAL (clicked ()), this, SLOT (slot_undo ()));
+	connect (redoButton, SIGNAL (clicked ()), this, SLOT (slot_redo ()));
+	connect (clearButton, SIGNAL (clicked ()), this, SLOT (slot_clear ()));
+	connect (buttons, SIGNAL (rejected ()), this, SLOT (reject ()));
+	connect (historyList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_selChanged ()));
+	
+	QVBoxLayout* qButtonLayout = new QVBoxLayout;
+	qButtonLayout->setDirection (QBoxLayout::TopToBottom);
+	qButtonLayout->addWidget (undoButton);
+	qButtonLayout->addWidget (redoButton);
+	qButtonLayout->addWidget (clearButton);
+	qButtonLayout->addStretch ();
+	
+	QGridLayout* qLayout = new QGridLayout;
+	qLayout->setColumnStretch (0, 1);
+	qLayout->addWidget (historyList, 0, 0, 2, 1);
+	qLayout->addLayout (qButtonLayout, 0, 1);
+	qLayout->addWidget (buttons, 1, 1);
+	
+	setLayout (qLayout);
+	setWindowIcon (getIcon ("history"));
+	setWindowTitle (APPNAME " - Edit history");
+	
+	populateList ();
+	updateButtons ();
+	updateSelection ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void HistoryDialog::populateList () {
+	historyList->clear ();
+	
+	QListWidgetItem* qItem = new QListWidgetItem;
+	qItem->setText ("[[ initial state ]]");
+	qItem->setIcon (getIcon ("empty"));
+	historyList->addItem (qItem);
+	
+	for (HistoryEntry* entry : History::entries ()) {
+		str text;
+		QIcon entryIcon;
+		
+		switch (entry->type ()) {
+		case HISTORY_Add:
+			{
+				AddHistory* subentry = static_cast<AddHistory*> (entry);
+				ulong count = subentry->paObjs.size ();
+				str verb = "Added";
+				
+				switch (subentry->eType) {
+				case AddHistory::Paste:
+					verb = "Pasted";
+					entryIcon = getIcon ("paste");
+					break;
+				
+				default:
+					{
+						// Determine a common type for these objects. If all objects are of the same
+						// type, we display its addition icon. Otherwise, we draw a subfile addition
+						// one as a default.
+						LDObject::Type eCommonType = LDObject::Unidentified;
+						for (LDObject* obj : subentry->paObjs) {
+							if (eCommonType == LDObject::Unidentified or obj->getType() == eCommonType)
+								eCommonType = obj->getType ();
+							else {
+								eCommonType = LDObject::Unidentified;
+								break;
+							}
+						}
+						
+						// Set the icon based on the common type decided above.
+						if (eCommonType == LDObject::Unidentified)
+							entryIcon = getIcon ("add-subfile");
+						else
+							entryIcon = getIcon (fmt ("add-%s", g_saObjTypeIcons[eCommonType]));
+					}
+					break;
+				}
+				
+				text.format ("%s %lu objects\n%s", verb.chars(), count,
+					LDObject::objectListContents (subentry->paObjs).chars());
+			}
+			break;
+		
+		case HISTORY_QuadSplit:
+			{
+				QuadSplitHistory* subentry = static_cast<QuadSplitHistory*> (entry);
+				ulong ulCount = subentry->paQuads.size ();
+				text.format ("Split %lu quad%s to triangles", ulCount, PLURAL (ulCount));
+				
+				entryIcon = getIcon ("quad-split");
+			}
+			break;
+		
+		case HISTORY_Del:
+			{
+				DelHistory* subentry = static_cast<DelHistory*> (entry);
+				ulong count = subentry->cache.size ();
+				str verb = "Deleted";
+				entryIcon = getIcon ("delete");
+				
+				switch (subentry->eType) {
+				case DelHistory::Cut:
+					entryIcon = getIcon ("cut");
+					verb = "Cut";
+					break;
+				
+				default:
+					break;
+				}
+				
+				text.format ("%s %lu objects:\n%s", verb.chars(), count,
+					LDObject::objectListContents (subentry->cache).chars ());
+			}
+			break;
+		
+		case HISTORY_SetColor:
+			{
+				SetColorHistory* subentry = static_cast<SetColorHistory*> (entry);
+				ulong count = subentry->ulaIndices.size ();
+				text.format ("Set color of %lu objects to %d (%s)", count,
+					subentry->dNewColor, getColor (subentry->dNewColor)->zName.chars());
+				
+				entryIcon = getIcon ("palette");
+			}
+			break;
+		
+		case HISTORY_ListMove:
+			{
+				ListMoveHistory* subentry = static_cast<ListMoveHistory*> (entry);
+				ulong ulCount = subentry->ulaIndices.size ();
+				
+				text.format ("Moved %lu objects %s", ulCount,
+					subentry->bUp ? "up" : "down");
+				entryIcon = getIcon (subentry->bUp ? "arrow-up" : "arrow-down");
+			}
+			break;
+		
+		case HISTORY_Edit:
+			{
+				EditHistory* subentry = static_cast<EditHistory*> (entry);
+				
+				text.format ("Edited %u objects\n%s",
+					subentry->paNewObjs.size(),
+					LDObject::objectListContents (subentry->paOldObjs).chars ());
+				entryIcon = getIcon ("set-contents");
+			}
+			break;
+		
+		case HISTORY_Inline:
+			{
+				InlineHistory* subentry = static_cast<InlineHistory*> (entry);
+				
+				text.format ("%s %lu subfiles:\n%lu resultants",
+					(subentry->bDeep) ? "Deep-inlined" : "Inlined",
+					(ulong) subentry->paRefs.size(),
+					(ulong) subentry->ulaBitIndices.size());
+				
+				entryIcon = getIcon (subentry->bDeep ? "inline-deep" : "inline");
+			}
+			break;
+		
+		default:
+			text = "???";
+			break;
+		}
+		
+		QListWidgetItem* item = new QListWidgetItem;
+		item->setText (text);
+		item->setIcon (entryIcon);
+		historyList->addItem (item);
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void HistoryDialog::slot_undo () {
+	History::undo ();
+	updateButtons ();
+	updateSelection ();
+}
+
+// =============================================================================
+void HistoryDialog::slot_redo () {
+	History::redo ();
+	updateButtons ();
+	updateSelection ();
+}
+
+void HistoryDialog::updateSelection () {
+	historyList->setCurrentItem (historyList->item (History::pos () + 1));
+}
+
+// =============================================================================
+void HistoryDialog::slot_clear () {
+	if (!confirm ("Are you sure you want to clear the edit history?\nThis cannot be undone."))
+		return; // Canceled
+	
+	History::clear ();
+	populateList ();
+	updateButtons ();
+}
+
+// =============================================================================
+void HistoryDialog::updateButtons () {
+	undoButton->setEnabled (ACTION (undo)->isEnabled ());
+	redoButton->setEnabled (ACTION (redo)->isEnabled ());
+}
+
+// =============================================================================
+void HistoryDialog::slot_selChanged () {
+	if (historyList->selectedItems ().size () != 1)
+		return;
+	
+	QListWidgetItem* qItem = historyList->selectedItems ()[0];
+	
+	// Find the index of the edit
+	long lIdx;
+	for (lIdx = 0; lIdx < historyList->count (); ++lIdx)
+		if (historyList->item (lIdx) == qItem)
+			break;
+	
+	// qHistoryList is 0-based, History is -1-based, thus decrement
+	// the index now
+	lIdx--;
+	
+	if (lIdx == History::pos ())
+		return;
+	
+	// Seek to the selected edit by repeadetly undoing or redoing.
+	while (History::pos () != lIdx) {
+		if (History::pos () > lIdx)
+			History::undo ();
+		else
+			History::redo ();
+	}
+	
+	updateButtons ();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/historyDialog.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,50 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HISTORYDIALOG_H
+#define HISTORYDIALOG_H
+
+#include <qdialog.h>
+#include <qdialogbuttonbox.h>
+#include <qlistwidget.h>
+#include <qpushbutton.h>
+#include "gui.h"
+
+class HistoryDialog : public QDialog {
+	Q_OBJECT
+	
+public:
+	explicit HistoryDialog (QWidget* parent = null, Qt::WindowFlags f = 0);
+	void populateList ();
+	
+	QListWidget* historyList;
+	QPushButton* undoButton, *redoButton, *clearButton;
+	QDialogButtonBox* buttons;
+	
+private:
+	void updateButtons ();
+	void updateSelection ();
+	
+private slots:
+	void slot_undo ();
+	void slot_redo ();
+	void slot_clear ();
+	void slot_selChanged ();
+};
+
+#endif // HISTORYDIALOG_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldrawPathDialog.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,129 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <qlineedit.h>
+#include <qpushbutton.h>
+#include <qdialogbuttonbox.h>
+#include <QFileDialog>
+#include "ldrawPathDialog.h"
+#include "gui.h"
+#include "file.h"
+
+extern_cfg (str, io_ldpath);
+
+// ========================================================================================================================================
+LDrawPathDialog::LDrawPathDialog (const bool validDefault, QWidget* parent, Qt::WindowFlags f)
+	: QDialog (parent, f), m_validDefault (validDefault)
+{
+	QLabel* lb_description;
+	lb_resolution = new QLabel ("---");
+	
+	if (validDefault == false)
+		lb_description = new QLabel ("Please input your LDraw directory");
+	
+	QLabel* lb_path = new QLabel ("LDraw path:");
+	le_path = new QLineEdit;
+	btn_findPath = new QPushButton;
+	btn_findPath->setIcon (getIcon ("folder"));
+	
+	btn_tryConfigure = new QPushButton ("Configure");
+	btn_tryConfigure->setIcon (getIcon ("settings"));
+	
+	btn_cancel = new QPushButton;
+	
+	if (validDefault == false) {
+		btn_cancel->setText ("Exit");
+		btn_cancel->setIcon (getIcon ("exit"));
+	} else {
+		btn_cancel->setText ("Cancel");
+		btn_cancel->setIcon (getIcon ("cancel"));
+	}
+	
+	dbb_buttons = new QDialogButtonBox (QDialogButtonBox::Ok);
+	dbb_buttons->addButton (btn_tryConfigure, QDialogButtonBox::ActionRole);
+	dbb_buttons->addButton (btn_cancel, QDialogButtonBox::RejectRole);
+	okButton ()->setEnabled (false);
+	
+	QHBoxLayout* inputLayout = new QHBoxLayout;
+	inputLayout->addWidget (lb_path);
+	inputLayout->addWidget (le_path);
+	inputLayout->addWidget (btn_findPath);
+	
+	QVBoxLayout* mainLayout = new QVBoxLayout;
+	
+	if (validDefault == false)
+		mainLayout->addWidget (lb_description);
+	
+	mainLayout->addLayout (inputLayout);
+	mainLayout->addWidget (lb_resolution);
+	mainLayout->addWidget (dbb_buttons);
+	setLayout (mainLayout);
+	
+	connect (le_path, SIGNAL (textEdited ()), this, SLOT (slot_tryConfigure ()));
+	connect (btn_findPath, SIGNAL (clicked ()), this, SLOT (slot_findPath ()));
+	connect (btn_tryConfigure, SIGNAL (clicked ()), this, SLOT (slot_tryConfigure ()));
+	connect (dbb_buttons, SIGNAL (accepted ()), this, SLOT (accept ()));
+	connect (dbb_buttons, SIGNAL (rejected ()), this, (validDefault) ? SLOT (reject ()) : SLOT (slot_exit ()));
+	
+	setPath (io_ldpath);
+	if (validDefault)
+		slot_tryConfigure ();
+}
+
+// ========================================================================================================================================
+QPushButton* LDrawPathDialog::okButton () {
+	return dbb_buttons->button (QDialogButtonBox::Ok);
+}
+
+// ========================================================================================================================================
+void LDrawPathDialog::setPath (str path) {
+	le_path->setText (path);
+}
+
+// ========================================================================================================================================
+str LDrawPathDialog::path () const {
+	return le_path->text ();
+}
+
+// ========================================================================================================================================
+void LDrawPathDialog::slot_findPath () {
+	str newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path");
+	
+	if (~newpath > 0 && newpath != path ()) {
+		setPath (newpath);
+		slot_tryConfigure ();
+	}
+}
+
+
+// ========================================================================================================================================
+void LDrawPathDialog::slot_exit () {
+	exit (1);
+}
+
+// ========================================================================================================================================
+void LDrawPathDialog::slot_tryConfigure () {
+	if (LDPaths::tryConfigure (path ()) == false) {
+		lb_resolution->setText (fmt ("<span style=\"color:red; font-weight: bold;\">%s</span>", LDPaths::getError().chars ()));
+		okButton ()->setEnabled (false);
+		return;
+	}
+	
+	lb_resolution->setText ("<span style=\"color: #7A0; font-weight: bold;\">OK!</span>");
+	okButton ()->setEnabled (true);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldrawPathDialog.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,57 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LDRAWPATHDIALOG_H
+#define LDRAWPATHDIALOG_H
+
+#include <qdialog.h>
+#include "common.h"
+
+class QLabel;
+class QLineEdit;
+class QDialogButtonBox;
+
+class LDrawPathDialog : public QDialog {
+	Q_OBJECT
+	
+public:
+	explicit LDrawPathDialog (const bool validDefault, QWidget* parent = null, Qt::WindowFlags f = 0);
+	str path () const;
+	void setPath (str path);
+	void (*callback ()) () const { return m_callback; }
+	void setCallback (void (*callback) ()) { m_callback = callback; }
+	
+private:
+	Q_DISABLE_COPY (LDrawPathDialog)
+	
+	QLabel* lb_resolution;
+	QLineEdit* le_path;
+	QPushButton* btn_findPath, *btn_tryConfigure, *btn_cancel;
+	QDialogButtonBox* dbb_buttons;
+	void (*m_callback) ();
+	const bool m_validDefault;
+	
+	QPushButton* okButton ();
+	
+private slots:
+	void slot_findPath ();
+	void slot_tryConfigure ();
+	void slot_exit ();
+};
+
+#endif // LDRAWPATHDIALOG_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldtypes.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,675 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+#include "ldtypes.h"
+#include "file.h"
+#include "misc.h"
+#include "gui.h"
+
+char const* g_saObjTypeNames[] = {
+	"subfile",
+	"radial",
+	"quadrilateral",
+	"triangle",
+	"line",
+	"condline",
+	"vertex",
+	"bfc",
+	"comment",
+	"unknown",
+	"empty",
+	"unidentified",
+};
+
+// Should probably get rid of this array sometime
+char const* g_saObjTypeIcons[] = {
+	"subfile",
+	"radial",
+	"quad",
+	"triangle",
+	"line",
+	"condline",
+	"vertex",
+	"bfc",
+	"comment",
+	"error",
+	"empty",
+	"error",
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+// LDObject constructors
+LDObject::LDObject () {
+	qObjListEntry = null;
+	parent = null;
+	m_hidden = false;
+}
+
+LDGibberish::LDGibberish (str _zContent, str _zReason) {
+	zContents = _zContent;
+	zReason = _zReason;
+}
+
+// =============================================================================
+str LDComment::getContents () {
+	return fmt ("0 %s", text.chars ());
+}
+
+str LDSubfile::getContents () {
+	str val = fmt ("1 %d %s ", dColor, vPosition.stringRep (false).chars ());
+	val += mMatrix.stringRep ();
+	val += ' ';
+	val += zFileName;
+	return val;
+}
+
+str LDLine::getContents () {
+	str val = fmt ("2 %d", dColor);
+	
+	for (ushort i = 0; i < 2; ++i)
+		val.appendformat (" %s", vaCoords[i].stringRep (false).chars ());
+	
+	return val;
+}
+
+str LDTriangle::getContents () {
+	str val = fmt ("3 %d", dColor);
+	
+	for (ushort i = 0; i < 3; ++i)
+		val.appendformat (" %s", vaCoords[i].stringRep (false).chars ());
+	
+	return val;
+}
+
+str LDQuad::getContents () {
+	str val = fmt ("4 %d", dColor);
+	
+	for (ushort i = 0; i < 4; ++i)
+		val.appendformat (" %s", vaCoords[i].stringRep (false).chars ());
+	
+	return val;
+}
+
+str LDCondLine::getContents () {
+	str val = fmt ("5 %d", dColor);
+	
+	// Add the coordinates
+	for (ushort i = 0; i < 4; ++i)
+		val.appendformat (" %s", vaCoords[i].stringRep (false).chars ());
+	
+	return val;
+}
+
+str LDGibberish::getContents () {
+	return zContents;
+}
+
+str LDVertex::getContents () {
+	return fmt ("0 !LDFORGE VERTEX %d %s", dColor, vPosition.stringRep (false).chars());
+}
+
+str LDEmpty::getContents () {
+	return str ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+const char* LDBFC::statements[] = {
+	"CERTIFY CCW",
+	"CCW",
+	"CERTIFY CW",
+	"CW",
+	"NOCERTIFY",
+	"INVERTNEXT",
+};
+
+str LDBFC::getContents () {
+	return fmt ("0 BFC %s", LDBFC::statements[type]);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+vector<LDTriangle*> LDQuad::splitToTriangles () {
+	// Create the two triangles based on this quadrilateral:
+	// 0---3     0---3    3
+	// |   |     |  /    /|
+	// |   |  =  | /    / |
+	// |   |     |/    /  |
+	// 1---2     1    1---2
+	LDTriangle* tri1 = new LDTriangle;
+	tri1->vaCoords[0] = vaCoords[0];
+	tri1->vaCoords[1] = vaCoords[1];
+	tri1->vaCoords[2] = vaCoords[3];
+	
+	LDTriangle* tri2 = new LDTriangle;
+	tri2->vaCoords[0] = vaCoords[1];
+	tri2->vaCoords[1] = vaCoords[2];
+	tri2->vaCoords[2] = vaCoords[3];
+	
+	// The triangles also inherit the quad's color
+	tri1->dColor = tri2->dColor = dColor;
+	
+	vector<LDTriangle*> triangles;
+	triangles.push_back (tri1);
+	triangles.push_back (tri2);
+	return triangles;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void LDObject::replace (LDObject* replacement) {
+	// Replace all instances of the old object with the new object
+	for (LDObject*& obj : g_curfile->m_objs)
+		if (obj == this)
+			obj = replacement;
+	
+	// Remove the old object
+	delete this;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void LDObject::swap (LDObject* other) {
+	for (LDObject*& obj : g_curfile->m_objs) {
+		if (obj == this)
+			obj = other;
+		else if (obj == other)
+			obj = this;
+	}
+}
+
+LDLine::LDLine (vertex v1, vertex v2) {
+	vaCoords[0] = v1;
+	vaCoords[1] = v2;
+}
+
+LDObject::~LDObject () {
+	// Remove this object from the selection array if it is there.
+	for (ulong i = 0; i < g_win->sel ().size(); ++i)
+		if (g_win->sel ()[i] == this)
+			g_win->sel ().erase (g_win->sel ().begin() + i);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static void transformObject (LDObject* obj, matrix<3> transform, vertex pos, short parentcolor) {
+	switch (obj->getType()) {
+	case LDObject::Line:
+	case LDObject::CondLine:
+	case LDObject::Triangle:
+	case LDObject::Quad:
+		for (short i = 0; i < obj->vertices (); ++i)
+			obj->vaCoords[i].transform (transform, pos);
+		break;
+	
+	case LDObject::Subfile:
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			
+			matrix<3> newMatrix = transform * ref->mMatrix;
+			ref->vPosition.transform (transform, pos);
+			ref->mMatrix = newMatrix;
+		}
+		break;
+	
+	default:
+		break;
+	}
+	
+	if (obj->dColor == maincolor)
+		obj->dColor = parentcolor;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+vector<LDObject*> LDSubfile::inlineContents (bool bDeepInline, bool bCache) {
+	vector<LDObject*> objs, cache;
+	
+	// If we have this cached, just clone that
+	if (bDeepInline && pFile->m_objCache.size ()) {
+		for (LDObject* obj : pFile->m_objCache)
+			objs.push_back (obj->clone ());
+	} else {
+		if (!bDeepInline)
+			bCache = false;
+		
+		for (LDObject* obj : pFile->m_objs) {
+			// Skip those without schemantic meaning
+			switch (obj->getType ()) {
+			case LDObject::Comment:
+			case LDObject::Empty:
+			case LDObject::Gibberish:
+			case LDObject::Unidentified:
+			case LDObject::Vertex:
+				continue;
+			
+			case LDObject::BFC:
+				// Filter non-INVERTNEXT statements
+				if (static_cast<LDBFC*> (obj)->type != LDBFC::InvertNext)
+					continue;
+				break;
+			
+			default:
+				break;
+			}
+			
+			// Got another sub-file reference, inline it if we're deep-inlining. If not,
+			// just add it into the objects normally. Also, we only cache immediate
+			// subfiles and this is not one. Yay, recursion!
+			if (bDeepInline && obj->getType() == LDObject::Subfile) {
+				LDSubfile* ref = static_cast<LDSubfile*> (obj);
+				
+				vector<LDObject*> otherobjs = ref->inlineContents (true, false);
+				
+				for (LDObject* otherobj : otherobjs) {
+					// Cache this object, if desired
+					if (bCache)
+						cache.push_back (otherobj->clone ());
+					
+					objs.push_back (otherobj);
+				}
+			} else {
+				if (bCache)
+					cache.push_back (obj->clone ());
+				
+				objs.push_back (obj->clone ());
+			}
+		}
+		
+		if (bCache)
+			pFile->m_objCache = cache;
+	}
+	
+	// Transform the objects
+	for (LDObject* obj : objs) {
+		// Set the parent now so we know what inlined this.
+		obj->parent = this;
+		
+		transformObject (obj, mMatrix, vPosition, dColor);
+	}
+	
+	return objs;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+long LDObject::getIndex (OpenFile* pFile) {
+	for (ulong i = 0; i < pFile->m_objs.size(); ++i)
+		if (pFile->m_objs[i] == this)
+			return i;
+	
+	return -1;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void LDObject::moveObjects (std::vector<LDObject*> objs, const bool bUp) {
+	// If we move down, we need to iterate the array in reverse order.
+	const long start = bUp ? 0 : (objs.size() - 1);
+	const long end = bUp ? objs.size() : -1;
+	const long incr = bUp ? 1 : -1;
+	
+	for (long i = start; i != end; i += incr) {
+		LDObject* obj = objs[i];
+		
+		const long lIndex = obj->getIndex (g_curfile),
+			lTarget = lIndex + (bUp ? -1 : 1);
+		
+		if ((bUp == true and lIndex == 0) or
+			(bUp == false and lIndex == (long)(g_curfile->m_objs.size() - 1)))
+		{
+			// One of the objects hit the extrema. If this happens, this should be the first
+			// object to be iterated on. Thus, nothing has changed yet and it's safe to just
+			// abort the entire operation.
+			assert (i == start);
+			return;
+		}
+		
+		obj->swap (g_curfile->m_objs[lTarget]);
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+str LDObject::objectListContents (const std::vector<LDObject*>& objs) {
+	bool firstDetails = true;
+	str text = "";
+	
+	if (objs.size() == 0)
+		return "nothing"; // :)
+	
+	for (long i = 0; i < LDObject::NumTypes; ++i) {
+		LDObject::Type objType = (LDObject::Type) i;
+		ulong objCount = 0;
+		
+		for (LDObject* obj : objs)
+			if (obj->getType() == objType)
+				objCount++;
+		
+		if (objCount == 0)
+			continue;
+		
+		if (!firstDetails)
+			text += ", ";
+		
+		str noun = fmt ("%s%s", g_saObjTypeNames[objType], PLURAL (objCount));
+		
+		// Plural of "vertex" is "vertices". Stupid English.
+		if (objType == LDObject::Vertex && objCount != 1)
+			noun = "vertices";
+		
+		text.appendformat ("%lu %s", objCount, noun.chars ());
+		firstDetails = false;
+	}
+	
+	return text;
+}
+
+// =============================================================================
+LDObject* LDObject::topLevelParent () {
+	if (!parent)
+		return null;
+	
+	LDObject* it = this;
+	
+	while (it->parent)
+		it = it->parent;
+	
+	return it;
+}
+
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void LDObject::move (vertex vVector) { vVector = vVector; /* to shut up GCC */ }
+void LDEmpty::move (vertex vVector) { vVector = vVector; }
+void LDBFC::move (vertex vVector) { vVector = vVector; }
+void LDComment::move (vertex vVector) { vVector = vVector; }
+void LDGibberish::move (vertex vVector) { vVector = vVector; }
+
+void LDVertex::move (vertex vVector) {
+	vPosition += vVector;
+}
+
+void LDSubfile::move (vertex vVector) {
+	vPosition += vVector;
+}
+
+void LDRadial::move (vertex vVector) {
+	vPosition += vVector;
+}
+
+void LDLine::move (vertex vVector) {
+	for (short i = 0; i < 2; ++i)
+		vaCoords[i] += vVector;
+}
+
+void LDTriangle::move (vertex vVector) {
+	for (short i = 0; i < 3; ++i)
+		vaCoords[i] += vVector;
+}
+
+void LDQuad::move (vertex vVector) {
+	for (short i = 0; i < 4; ++i)
+		vaCoords[i] += vVector;
+}
+
+void LDCondLine::move (vertex vVector) {
+	for (short i = 0; i < 4; ++i)
+		vaCoords[i] += vVector;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static char const* g_saRadialTypeNames[] = {
+	"Circle",
+	"Cylinder",
+	"Disc",
+	"Disc Negative",
+	"Ring",
+	"Cone",
+	null
+};
+
+char const* LDRadial::radialTypeName () {
+	return g_saRadialTypeNames[eRadialType];
+}
+
+char const* LDRadial::radialTypeName (const LDRadial::Type eType) {
+	return g_saRadialTypeNames[eType];
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+std::vector<LDObject*> LDRadial::decompose (bool bTransform) {
+	std::vector<LDObject*> paObjects;
+	
+	for (short i = 0; i < dSegments; ++i) {
+		double x0 = cos ((i * 2 * pi) / dDivisions),
+			x1 = cos (((i + 1) * 2 * pi) / dDivisions),
+			z0 = sin ((i * 2 * pi) / dDivisions),
+			z1 = sin (((i + 1) * 2 * pi) / dDivisions);
+		
+		switch (eRadialType) {
+		case LDRadial::Circle:
+			{
+				vertex v0 (x0, 0.0f, z0),
+					v1 (x1, 0.0f, z1);
+				
+				if (bTransform) {
+					v0.transform (mMatrix, vPosition);
+					v1.transform (mMatrix, vPosition);
+				}
+				
+				LDLine* pLine = new LDLine;
+				pLine->vaCoords[0] = v0;
+				pLine->vaCoords[1] = v1;
+				pLine->dColor = edgecolor;
+				pLine->parent = this;
+				
+				paObjects.push_back (pLine);
+			}
+			break;
+		
+		case LDRadial::Cylinder:
+		case LDRadial::Ring:
+		case LDRadial::Cone:
+			{
+				double x2, x3, z2, z3;
+				double y0, y1, y2, y3;
+				
+				if (eRadialType == LDRadial::Cylinder) {
+					x2 = x1;
+					x3 = x0;
+					z2 = z1;
+					z3 = z0;
+					
+					y0 = y1 = 0.0f;
+					y2 = y3 = 1.0f;
+				} else {
+					x2 = x1 * (dRingNum + 1);
+					x3 = x0 * (dRingNum + 1);
+					z2 = z1 * (dRingNum + 1);
+					z3 = z0 * (dRingNum + 1);
+					
+					x0 *= dRingNum;
+					x1 *= dRingNum;
+					z0 *= dRingNum;
+					z1 *= dRingNum;
+					
+					if (eRadialType == LDRadial::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);
+				
+				if (bTransform) {
+					v0.transform (mMatrix, vPosition);
+					v1.transform (mMatrix, vPosition);
+					v2.transform (mMatrix, vPosition);
+					v3.transform (mMatrix, vPosition);
+				}
+				
+				LDQuad* pQuad = new LDQuad;
+				pQuad->vaCoords[0] = v0;
+				pQuad->vaCoords[1] = v1;
+				pQuad->vaCoords[2] = v2;
+				pQuad->vaCoords[3] = v3;
+				pQuad->dColor = dColor;
+				pQuad->parent = this;
+				
+				paObjects.push_back (pQuad);
+			}
+			break;
+		
+		case LDRadial::Disc:
+		case LDRadial::DiscNeg:
+			{
+				double x2, z2;
+				
+				if (eRadialType == LDRadial::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);
+				
+				if (bTransform) {
+					v0.transform (mMatrix, vPosition);
+					v1.transform (mMatrix, vPosition);
+					v2.transform (mMatrix, vPosition);
+				}
+				
+				LDTriangle* pSeg = new LDTriangle;
+				pSeg->vaCoords[0] = v0;
+				pSeg->vaCoords[1] = v1;
+				pSeg->vaCoords[2] = v2;
+				pSeg->dColor = dColor;
+				pSeg->parent = this;
+				
+				paObjects.push_back (pSeg);
+			}
+			break;
+		
+		default:
+			break;
+		}
+	}
+	
+	return paObjects;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+str LDRadial::getContents () {
+	return fmt ("0 !LDFORGE RADIAL %s %d %d %d %d %s %s",
+		str (radialTypeName()).toupper ().strip (' ').chars (),
+		dColor, dSegments, dDivisions, dRingNum,
+		vPosition.stringRep (false).chars(), mMatrix.stringRep().chars());
+}
+
+char const* g_saRadialNameRoots[] = {
+	"edge",
+	"cyli",
+	"disc",
+	"ndis",
+	"ring",
+	"cone",
+	null
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+str LDRadial::makeFileName () {
+	short numer = dSegments,
+		denom = dDivisions;
+	
+	// Simplify the fractional part, but the denominator must be at least 4.
+	simplify (numer, denom);
+	
+	if (denom < 4) {
+		const short factor = (4 / denom);
+		
+		numer *= factor;
+		denom *= factor;
+	}
+	
+	// Compose some general information: prefix, fraction, root, ring number
+	str prefix = (dDivisions == 16) ? "" : fmt ("%d/", dDivisions);
+	str frac = fmt ("%d-%d", numer, denom);
+	str root = g_saRadialNameRoots[eRadialType];
+	str ringNum = (eRadialType == Ring || eRadialType == Cone) ? fmt ("%d", dRingNum) : "";
+	
+	// Truncate the root if necessary (7-16rin4.dat for instance).
+	// However, always keep the root at least 2 characters.
+	short extra = (~frac + ~ringNum + ~root) - 8;
+	root -= min<short> (max<short> (extra, 0), 2);
+	
+	// Stick them all together and return the result.
+	return fmt ("%s%s%s%s", prefix.chars(), frac.chars (), root.chars (), ringNum.chars ());
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+#define CHECK_FOR_OBJ(N) \
+	if (type == LDObject::N) \
+		return new LD##N;
+LDObject* LDObject::getDefault (const LDObject::Type type) {
+	CHECK_FOR_OBJ (Comment)
+	CHECK_FOR_OBJ (BFC)
+	CHECK_FOR_OBJ (Line)
+	CHECK_FOR_OBJ (CondLine)
+	CHECK_FOR_OBJ (Radial)
+	CHECK_FOR_OBJ (Subfile)
+	CHECK_FOR_OBJ (Triangle)
+	CHECK_FOR_OBJ (Quad)
+	CHECK_FOR_OBJ (Empty)
+	CHECK_FOR_OBJ (BFC)
+	CHECK_FOR_OBJ (Gibberish)
+	CHECK_FOR_OBJ (Vertex)
+	return null;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldtypes.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,387 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LDTYPES_H
+#define LDTYPES_H
+
+#include "common.h"
+#include "types.h"
+
+#define IMPLEMENT_LDTYPE(T, NUMVERTS) \
+	LD##T () {} \
+	virtual ~LD##T () {} \
+	virtual LDObject::Type getType () const { \
+		return LDObject::T; \
+	} \
+	virtual str getContents (); \
+	virtual LD##T* clone () { \
+		return new LD##T (*this); \
+	} \
+	virtual void move (vertex vVector); \
+	virtual short vertices () const { return NUMVERTS; } \
+
+#define LDOBJ_SETCOLORED(V) virtual bool isColored () const { return V; }
+#define LDOBJ_COLORED LDOBJ_SETCOLORED (true)
+#define LDOBJ_UNCOLORED LDOBJ_SETCOLORED (false)
+
+#define LDOBJ_CUSTOM_SCHEMANTIC virtual bool isSchemantic () const
+#define LDOBJ_SCHEMANTIC LDOBJ_CUSTOM_SCHEMANTIC { return true; }
+#define LDOBJ_NON_SCHEMANTIC LDOBJ_CUSTOM_SCHEMANTIC { return false; }
+
+class QListWidgetItem;
+class LDSubfile;
+
+// =============================================================================
+// LDObject
+// 
+// Base class object for all LD* types. Each LDObject represents a single line
+// in the LDraw code file. The virtual method getType returns an enumerator
+// which is a token of the object's type. The object can be casted into
+// sub-classes based on this enumerator.
+// =============================================================================
+class LDObject {
+public:
+	// Object type codes. Codes are sorted in order of significance.
+	enum Type {
+		Subfile,		// Object represents a sub-file reference
+		Radial,			// Object represents a generic radial
+		Quad,			// Object represents a quadrilateral
+		Triangle,		// Object represents a triangle
+		Line,			// Object represents a line
+		CondLine,		// Object represents a conditional line
+		Vertex,			// Object is a vertex, LDForge extension object
+		BFC,			// Object represents a BFC statement
+		Comment,		// Object represents a comment
+		Gibberish,		// Object is the result of failed parsing
+		Empty,			// Object represents an empty line
+		Unidentified,	// Object is an uninitialized (SHOULD NEVER HAPPEN)
+		NumTypes		// Amount of object types
+	};
+	
+	LDObject ();
+	virtual ~LDObject ();
+	
+	// Index (i.e. line number) of this object
+	long getIndex (OpenFile* pFile);
+	
+	// Color used by this object. Comments, gibberish and empty entries
+	// do not use this field.
+	short dColor;
+	
+	// OpenGL list for this object
+	uint uGLList, uGLPickList;
+	
+	// Vertices of this object
+	vertex vaCoords[4];
+	
+	// Object this object was referenced from, if any
+	LDObject* parent;
+	
+	// Type enumerator of this object
+	virtual LDObject::Type getType () const {
+		return LDObject::Unidentified;
+	};
+	
+	// A string that represents this line
+	virtual str getContents () {
+		return "";
+	}
+	
+	// Creates a new LDObject identical to this one and returns a pointer to it.
+	virtual LDObject* clone () {
+		return new LDObject (*this);
+	}
+	
+	// Replace this LDObject with another LDObject. This method deletes the
+	// object and any pointers to it become invalid.
+    void replace (LDObject* replacement);
+	
+	// Swap this object with another.
+	void swap (LDObject* other);
+	
+	// Moves this object using the given vertex as a movement vector
+	virtual void move (vertex vVector);
+	
+	// What object in the current file ultimately references this?
+	LDObject* topLevelParent ();
+	
+	// Number of vertices this object has
+	virtual short vertices () const { return 0; }
+	
+	// Is this object colored?
+	virtual bool isColored () const { return false; }
+	
+	// Does this object have meaning in the part model?
+	virtual bool isSchemantic () const { return false; }
+	
+	// Returns a sample object by the given value
+	static LDObject* getDefault (const LDObject::Type type);
+	
+	static void moveObjects (std::vector<LDObject*> objs, const bool bUp);
+	static str objectListContents (const std::vector<LDObject*>& objs);
+	
+	// Object list entry for this object
+	QListWidgetItem* qObjListEntry;
+	
+	bool hidden () const { return m_hidden; }
+	void setHidden (const bool hidden) { m_hidden = hidden; }
+
+private:
+	bool m_hidden;
+};
+
+// =============================================================================
+// LDGibberish
+//
+// Represents a line in the LDraw file that could not be properly parsed. It is
+// represented by a (!) ERROR in the code view. It exists for the purpose of
+// allowing garbage lines be debugged and corrected within LDForge. The member
+// zContent contains the contents of the unparsable line.
+// =============================================================================
+class LDGibberish : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Gibberish, 0)
+	LDOBJ_UNCOLORED
+	LDOBJ_SCHEMANTIC
+	
+	LDGibberish (str _zContent, str _zReason);
+	
+	// Content of this unknown line
+	str zContents;
+	
+	// Why is this gibberish?
+	str zReason;
+};
+
+// =============================================================================
+// LDEmptyLine 
+// 
+// Represents an empty line in the LDraw code file.
+// =============================================================================
+class LDEmpty : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Empty, 0)
+	LDOBJ_UNCOLORED
+	LDOBJ_NON_SCHEMANTIC
+};
+
+// =============================================================================
+// LDComment
+//
+// Represents a code-0 comment in the LDraw code file. Member zText contains
+// the text of the comment.
+// =============================================================================
+class LDComment : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Comment, 0)
+	LDOBJ_UNCOLORED
+	LDOBJ_NON_SCHEMANTIC
+	
+	LDComment (str zText) : text (zText) {}
+	
+	str text; // The text of this comment
+};
+
+// =============================================================================
+// LDBFC
+// 
+// Represents a 0 BFC statement in the LDraw code. eStatement contains the type
+// of this statement.
+// =============================================================================
+class LDBFC : public LDComment {
+public:
+	enum Type {
+		CertifyCCW,
+		CCW,
+		CertifyCW,
+		CW,
+		NoCertify,	// Winding becomes disabled (0 BFC NOCERTIFY)
+		InvertNext,	// Winding is inverted for next object (0 BFC INVERTNEXT)
+		NumStatements
+	};
+	
+	IMPLEMENT_LDTYPE (BFC, 0)
+	LDOBJ_UNCOLORED
+	LDOBJ_CUSTOM_SCHEMANTIC { return (type == InvertNext); }
+	
+	LDBFC (const LDBFC::Type eType) : type (eType) {}
+	
+	// Statement strings
+	static const char* statements[];
+	
+	Type type;
+};
+
+// =============================================================================
+// LDSubfile
+//
+// Represents a single code-1 subfile reference.
+// =============================================================================
+class LDSubfile : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Subfile, 0)
+	LDOBJ_COLORED
+	LDOBJ_SCHEMANTIC
+	
+	vertex vPosition; // Position of the subpart (FIXME: should get rid of this)
+	matrix<3> mMatrix; // Transformation matrix for the subpart
+	str zFileName; // Filename of the subpart
+	OpenFile* pFile; // Pointer to opened file for this subfile. null if unopened.
+	
+	// Inlines this subfile. Note that return type is an array of heap-allocated
+	// LDObject-clones, they must be deleted one way or another.
+	std::vector<LDObject*> inlineContents (bool bDeepInline, bool bCache);
+};
+
+// =============================================================================
+// LDLine
+//
+// Represents a single code-2 line in the LDraw code file. v0 and v1 are the end
+// points of the line. The line is colored with dColor unless uncolored mode is
+// set.
+// =============================================================================
+class LDLine : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Line, 2)
+	LDOBJ_COLORED
+	LDOBJ_SCHEMANTIC
+	
+	LDLine (vertex v1, vertex v2);
+};
+
+// =============================================================================
+// LDCondLine
+//
+// Represents a single code-5 conditional line. The end-points v0 and v1 are
+// inherited from LDLine, c0 and c1 are the control points of this line.
+// =============================================================================
+class LDCondLine : public LDLine {
+public:
+	IMPLEMENT_LDTYPE (CondLine, 4)
+	LDOBJ_COLORED
+	LDOBJ_SCHEMANTIC
+};
+
+// =============================================================================
+// LDTriangle
+//
+// Represents a single code-3 triangle in the LDraw code file. Vertices v0, v1
+// and v2 contain the end-points of this triangle. dColor is the color the
+// triangle is colored with.
+// =============================================================================
+class LDTriangle : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Triangle, 3)
+	LDOBJ_COLORED
+	LDOBJ_SCHEMANTIC
+	
+	LDTriangle (vertex _v0, vertex _v1, vertex _v2) {
+		vaCoords[0] = _v0;
+		vaCoords[1] = _v1;
+		vaCoords[2] = _v2;
+	}
+};
+
+// =============================================================================
+// LDQuad
+//
+// Represents a single code-4 quadrilateral. v0, v1, v2 and v3 are the end points
+// of the quad, dColor is the color used for the quad.
+// =============================================================================
+class LDQuad : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Quad, 4)
+	LDOBJ_COLORED
+	LDOBJ_SCHEMANTIC
+	
+	// Split this quad into two triangles (note: heap-allocated)
+	vector<LDTriangle*> splitToTriangles ();
+};
+
+// =============================================================================
+// LDVertex
+// 
+// The vertex is an LDForce-specific extension which represents a single
+// vertex which can be used as a parameter to tools or to store coordinates
+// with. Vertices are a part authoring tool and they should not appear in
+// finished parts.
+// =============================================================================
+class LDVertex : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Vertex, 0) // TODO: move vPosition to vaCoords[0]
+	LDOBJ_COLORED
+	LDOBJ_NON_SCHEMANTIC
+	
+	vertex vPosition;
+};
+
+// =============================================================================
+// LDRadial
+// 
+// The generic radial primitive (radial for short) is another LDforge-specific
+// extension which represents an arbitrary circular primitive. Radials can appear
+// as circles, cylinders, rings, cones, discs and disc negatives; the point is to
+// allow part authors to add radial primitives to parts without much hassle about
+// non-existant primitive parts.
+// =============================================================================
+class LDRadial : public LDObject {
+public:
+	enum Type {
+		Circle,
+		Cylinder,
+		Disc,
+		DiscNeg,
+		Ring,
+		Cone,
+		NumTypes
+	};
+	
+	IMPLEMENT_LDTYPE (Radial, 0)
+	LDOBJ_COLORED
+	LDOBJ_SCHEMANTIC
+	
+	LDRadial::Type eRadialType;
+	vertex vPosition;
+	matrix<3> mMatrix;
+	short dDivisions, dSegments, dRingNum;
+	
+	LDRadial (LDRadial::Type eRadialType, vertex vPosition, matrix<3> mMatrix,
+		short dDivisions, short dSegments, short dRingNum) :
+		eRadialType (eRadialType), vPosition (vPosition), mMatrix (mMatrix),
+		dDivisions (dDivisions), dSegments (dSegments), dRingNum (dRingNum) {}
+	
+	// Returns a set of objects that provide the equivalent of this radial.
+	// Note: objects are heap-allocated.
+	std::vector<LDObject*> decompose (bool bTransform);
+	
+	// Compose a file name for this radial.
+	str makeFileName ();
+	
+	char const* radialTypeName ();
+	static char const* radialTypeName (const LDRadial::Type eType);
+};
+
+// =============================================================================
+// Object type names. Pass the return value of getType as the index to get a
+// string representation of the object's type.
+extern const char* g_saObjTypeNames[];
+
+// Icons for these types
+extern const char* g_saObjTypeIcons[];
+
+#endif // LDTYPES_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,104 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QApplication>
+#include "gui.h"
+#include "file.h"
+#include "bbox.h"
+#include "misc.h"
+#include "config.h"
+#include "colors.h"
+#include "types.h"
+
+vector<OpenFile*> g_loadedFiles;
+OpenFile* g_curfile = null;
+ForgeWindow* g_win = null; 
+bbox g_BBox;
+const QApplication* g_app = null;
+
+const vertex g_origin (0.0f, 0.0f, 0.0f);
+const matrix<3> g_identity ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+int main (int argc, char* argv[]) {
+	// Load or create the configuration
+	if (!config::load()) {
+		printf ("Creating configuration file...\n");
+		if (config::save ())
+			printf ("Configuration file successfully created.\n");
+		else
+			printf ("failed to create configuration file!\n");
+	}
+	
+	const QApplication app (argc, argv);
+	LDPaths::initPaths ();
+	
+	initColors ();
+	initPartList ();
+	
+	ForgeWindow* win = new ForgeWindow;
+	
+	g_app = &app;
+	
+	newFile ();
+	
+	win->show ();
+	return app.exec ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void logf (const char* fmtstr, ...) {
+	va_list va;
+	va_start (va, fmtstr);
+	g_win->logVA (LOG_Normal, fmtstr, va);
+	va_end (va);
+}
+
+void logf (LogType type, const char* fmtstr, ...) {
+	va_list va;
+	va_start (va, fmtstr);
+	g_win->logVA (type, fmtstr, va);
+	va_end (va);
+}
+
+void warnf (const char* fmtstr, ...) {
+	va_list va;
+	va_start (va, fmtstr);
+	g_win->logVA (LOG_Warning, fmtstr, va);
+	va_end (va);
+}
+
+void errf (const char* fmtstr, ...) {
+	va_list va;
+	va_start (va, fmtstr);
+	g_win->logVA (LOG_Error, fmtstr, va);
+	va_end (va);
+}
+
+#ifndef RELEASE
+void devf (const char* fmtstr, ...) {
+	va_list va;
+	va_start (va, fmtstr);
+	g_win->logVA (LOG_Dev, fmtstr, va);
+	va_end (va);
+}
+#endif // RELEASE
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,277 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+#include <locale.h>
+#include <qcolor.h>
+#include "common.h"
+#include "misc.h"
+#include "gui.h"
+
+// Prime number table.
+const ushort g_uaPrimes[NUM_PRIMES] = {
+	   2,    3,    5,    7,   11,   13,   17,   19,   23,   29, 
+	  31,   37,   41,   43,   47,   53,   59,   61,   67,   71, 
+	  73,   79,   83,   89,   97,  101,  103,  107,  109,  113, 
+	 127,  131,  137,  139,  149,  151,  157,  163,  167,  173, 
+	 179,  181,  191,  193,  197,  199,  211,  223,  227,  229, 
+	 233,  239,  241,  251,  257,  263,  269,  271,  277,  281, 
+	 283,  293,  307,  311,  313,  317,  331,  337,  347,  349, 
+	 353,  359,  367,  373,  379,  383,  389,  397,  401,  409, 
+	 419,  421,  431,  433,  439,  443,  449,  457,  461,  463, 
+	 467,  479,  487,  491,  499,  503,  509,  521,  523,  541, 
+	 547,  557,  563,  569,  571,  577,  587,  593,  599,  601, 
+	 607,  613,  617,  619,  631,  641,  643,  647,  653,  659, 
+	 661,  673,  677,  683,  691,  701,  709,  719,  727,  733, 
+	 739,  743,  751,  757,  761,  769,  773,  787,  797,  809, 
+	 811,  821,  823,  827,  829,  839,  853,  857,  859,  863, 
+	 877,  881,  883,  887,  907,  911,  919,  929,  937,  941, 
+	 947,  953,  967,  971,  977,  983,  991,  997, 1009, 1013, 
+	1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 
+	1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 
+	1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 
+	1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 
+	1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 
+	1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 
+	1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 
+	1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 
+	1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 
+	1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 
+	1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 
+	1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 
+	1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 
+	1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 
+	2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 
+	2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 
+	2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 
+	2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 
+	2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 
+	2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 
+	2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 
+	2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 
+	2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 
+	2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 
+	2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 
+	2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 
+	3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, 
+	3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 
+	3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 
+	3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 
+	3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 
+	3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 
+	3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+// Grid stuff
+cfg (int, grid, Grid::Medium);
+EXTERN_ACTION (gridCoarse)
+EXTERN_ACTION (gridMedium)
+EXTERN_ACTION (gridFine)
+
+cfg (float, grid_coarse_x,			5.0f);
+cfg (float, grid_coarse_y,			5.0f);
+cfg (float, grid_coarse_z,			5.0f);
+cfg (float, grid_coarse_angle,	45.0f);
+cfg (float, grid_medium_x,			1.0f);
+cfg (float, grid_medium_y,			1.0f);
+cfg (float, grid_medium_z,			1.0f);
+cfg (float, grid_medium_angle,	22.5f);
+cfg (float, grid_fine_x,			0.1f);
+cfg (float, grid_fine_y,			0.1f);
+cfg (float, grid_fine_z,			0.1f);
+cfg (float, grid_fine_angle,		7.5f);
+
+const gridinfo g_GridInfo[3] = {
+	{ "Coarse",	{ &grid_coarse_x,	&grid_coarse_y,	&grid_coarse_z,	&grid_coarse_angle	}, &ACTION (gridCoarse) },
+	{ "Medium",	{ &grid_medium_x,	&grid_medium_y,		&grid_medium_z,		&grid_medium_angle	}, &ACTION (gridMedium) },
+	{ "Fine",		{ &grid_fine_x,	&grid_fine_y,		&grid_fine_z,		&grid_fine_angle	}, &ACTION (gridFine) }
+};
+
+// =============================================================================
+double Grid::snap (double in, const Grid::Config axis) {
+	const double gridval = currentGrid ().confs[axis]->value;
+	const long mult = abs (in / gridval);
+	const bool neg = (in < 0);
+	
+	double out = mult * gridval;
+	
+	if (abs<double> (in) - (mult * gridval) > gridval / 2)
+		out += gridval;
+	
+	if (neg && out != 0)
+		out *= -1;
+	
+	return out;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+str ftoa (double fCoord) {
+	// Disable the locale first so that the decimal point will not
+	// turn into anything weird (like commas)
+	setlocale (LC_NUMERIC, "C");
+	
+	str zRep = fmt ("%f", fCoord);
+	
+	// Remove trailing zeroes
+	while (zRep[~zRep - 1] == '0')
+		zRep -= 1;
+	
+	// If there was only zeroes in the decimal place, remove
+	// the decimal point now.
+	if (zRep[~zRep - 1] == '.')
+		zRep -= 1;
+	
+	return zRep;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+bool isNumber (str& zToken) {
+	char* cpPointer = &zToken[0];
+	bool bGotDot = false;
+	
+	// Allow leading hyphen for negatives
+	if (*cpPointer == '-')
+		cpPointer++;
+	
+	while (*cpPointer != '\0') {
+		if (*cpPointer == '.' && !bGotDot) {
+			// Decimal point
+			bGotDot = true;
+			cpPointer++;
+			continue;
+		}
+		
+		if (*cpPointer >= '0' && *cpPointer <= '9') {
+			cpPointer++;
+			continue; // Digit
+		}
+		
+		// If the above cases didn't catch this character, it was
+		// illegal and this is therefore not a number.
+		return false;
+	}
+	
+	return true;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void simplify (short& dNum, short& dDenom) {
+	bool bRepeat;
+	
+	do {
+		bRepeat = false;
+		
+		for (ulong x = 0; x < NUM_PRIMES; x++) {
+			ulong i = NUM_PRIMES - x - 1;
+			ushort uPrime = g_uaPrimes[i];
+			
+			if (dNum <= uPrime || dDenom <= uPrime)
+				continue;
+			
+			if ((dNum % uPrime == 0) && (dDenom % uPrime == 0)) {
+				dNum /= uPrime;
+				dDenom /= uPrime;
+				bRepeat = true;
+				break;
+			}
+		}
+	} while (bRepeat);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+StringParser::StringParser (str zInText, char cSeparator) {
+	zaTokens = zInText.split (cSeparator, true);
+	dPos = -1;
+}
+
+// -----------------------------------------------------------------------------
+bool StringParser::atBeginning () {
+	return (dPos == -1);
+}
+
+// -----------------------------------------------------------------------------
+bool StringParser::atEnd () {
+	return (dPos == (signed) zaTokens.size () - 1);
+}
+
+// -----------------------------------------------------------------------------
+bool StringParser::getToken (str& zVal, const ushort uInPos) {
+	if (uInPos >= zaTokens.size())
+		return false;
+	
+	zVal = zaTokens[uInPos];
+	return true;
+}
+
+// -----------------------------------------------------------------------------
+bool StringParser::next (str& zVal) {
+	return getToken (zVal, ++dPos);
+}
+
+// -----------------------------------------------------------------------------
+bool StringParser::peekNext (str& zVal) {
+	return getToken (zVal, dPos + 1);
+}
+
+// -----------------------------------------------------------------------------
+bool StringParser::findToken (short& dResult, char const* sNeedle, short dArgs) {
+	for (ushort i = 0; i < (zaTokens.size () - dArgs); ++i) {
+		if (zaTokens[i] == sNeedle) {
+			dResult = i;
+			return true;
+		}
+	}
+	
+	return false;
+}
+
+// -----------------------------------------------------------------------------
+void StringParser::rewind () {
+	dPos = -1;
+}
+
+// -----------------------------------------------------------------------------
+void StringParser::seek (short int dAmount, bool bRelative) {
+	dPos = (bRelative ? dPos : 0) + dAmount;
+}
+
+// -----------------------------------------------------------------------------
+size_t StringParser::size () {
+	return zaTokens.size();
+}
+
+// -----------------------------------------------------------------------------
+bool StringParser
+::tokenCompare (short int dInPos, const char* sOther) {
+	str tok;
+	if (!getToken (tok, dInPos))
+		return false;
+	
+	return (tok == sOther);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,110 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MISC_H
+#define MISC_H
+
+#include "common.h"
+#include "str.h"
+#include "config.h"
+
+#define NUM_PRIMES 500
+
+class QColor;
+class QAction;
+
+// Prime numbers
+extern const ushort g_uaPrimes[NUM_PRIMES];
+
+// Returns whether a given string represents a floating point number.
+bool isNumber (str& zToken);
+
+// Converts a float value to a string value.
+str ftoa (double fCoord);
+
+// Simplifies the given fraction.
+void simplify (short& dNum, short& dDenom);
+
+// Grid stuff
+typedef struct {
+	const char* const name;
+	floatconfig* const confs[4];
+	QAction** const actionptr;
+} gridinfo;
+
+extern_cfg (int, grid);
+static const short g_NumGrids = 3;
+extern const gridinfo g_GridInfo[3];
+
+inline const gridinfo& currentGrid () {
+	return g_GridInfo[grid];
+}
+
+namespace Grid {
+	enum Type {
+		Coarse,
+		Medium,
+		Fine
+	};
+	
+	enum Config {
+		X,
+		Y,
+		Z,
+		Angle
+	};
+	
+	double snap (double value, const Grid::Config axis);
+};
+
+// =============================================================================
+template<class T> void dataswap (T& a, T& b) {
+	T c = a;
+	a = b;
+	b = c;
+}
+
+// =============================================================================
+// StringParser
+//
+// String parsing utility
+// =============================================================================
+class StringParser {
+public:
+	std::vector<str> zaTokens;
+	short dPos;
+	
+	StringParser (str zInText, char cSeparator);
+	
+	bool atEnd ();
+	bool atBeginning ();
+	bool next (str& zVal);
+	bool peekNext (str& zVal);
+	bool getToken (str& zVal, const ushort uInPos);
+	bool findToken (short& dResult, char const* sNeedle, short dArgs);
+	size_t size ();
+	void rewind ();
+	void seek (short dAmount, bool bRelative);
+	bool tokenCompare (short int dInPos, const char* sOther);
+	
+	str operator[] (const size_t uIndex) {
+		return zaTokens[uIndex];
+	}
+};
+
+#endif // MISC_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/newPartDialog.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,121 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <qgridlayout.h>
+#include "newPartDialog.h"
+#include "file.h"
+
+// -------------------------------------
+enum {
+	LICENSE_CCAL,
+	LICENSE_NonCA,
+	LICENSE_None
+};
+
+// -------------------------------------
+enum {
+	BFCBOX_CCW,
+	BFCBOX_CW,
+	BFCBOX_None,
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+NewPartDialog::NewPartDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) {
+	lb_brickIcon = new QLabel;
+	lb_brickIcon->setPixmap (getIcon ("brick"));
+	
+	lb_name = new QLabel ("Name:");
+	le_name = new QLineEdit;
+	le_name->setMinimumWidth (320);
+	
+	lb_author = new QLabel ("Author:");
+	le_author = new QLineEdit;
+	
+	rb_license = new RadioBox ("License", {
+		"CCAL Redistributable",
+		"Non-redistributable",
+		"Don't append a license",
+	}, LICENSE_CCAL);
+	
+	rb_BFC = new RadioBox ("BFC Winding", {
+		"CCW",
+		"CW",
+		"No winding"
+	}, BFCBOX_CCW);
+	
+	QHBoxLayout* boxes = new QHBoxLayout;
+	boxes->addWidget (rb_license);
+	boxes->addWidget (rb_BFC);
+	
+	IMPLEMENT_DIALOG_BUTTONS
+	
+	QGridLayout* layout = new QGridLayout;
+	layout->addWidget (lb_brickIcon, 0, 0);
+	layout->addWidget (lb_name, 0, 1);
+	layout->addWidget (le_name, 0, 2);
+	layout->addWidget (lb_author, 1, 1);
+	layout->addWidget (le_author, 1, 2);
+	layout->addLayout (boxes, 2, 1, 1, 2);
+	layout->addWidget (bbx_buttons, 3, 2);
+	
+	setLayout (layout);
+	setWindowIcon (getIcon ("brick"));
+	setWindowTitle (APPNAME ": New Part");
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void NewPartDialog::StaticDialog () {
+	NewPartDialog dlg (g_win);
+	if (dlg.exec ()) {
+		newFile ();
+		
+		short idx;
+		str zAuthor = dlg.le_author->text ();
+		vector<LDObject*>& objs = g_curfile->m_objs;
+		
+		idx = dlg.rb_BFC->value ();
+		const LDBFC::Type eBFCType =
+			(idx == BFCBOX_CCW) ? LDBFC::CertifyCCW :
+			(idx == BFCBOX_CW) ? LDBFC::CertifyCW :
+			LDBFC::NoCertify;
+		
+		idx = dlg.rb_license->value ();
+		const char* sLicense =
+			(idx == LICENSE_CCAL) ? "Redistributable under CCAL version 2.0 : see CAreadme.txt" :
+			(idx == LICENSE_NonCA) ? "Not redistributable : see NonCAreadme.txt" :
+			null;
+		
+		objs.push_back (new LDComment (dlg.le_name->text ()));
+		objs.push_back (new LDComment ("Name: <untitled>.dat"));
+		objs.push_back (new LDComment (fmt ("Author: %s", zAuthor.chars())));
+		objs.push_back (new LDComment (fmt ("!LDRAW_ORG Unofficial_Part")));
+		
+		if (sLicense != null)
+			objs.push_back (new LDComment (fmt ("!LICENSE %s", sLicense)));
+		
+		objs.push_back (new LDEmpty);
+		objs.push_back (new LDBFC (eBFCType));
+		objs.push_back (new LDEmpty);
+		
+		g_win->refresh ();
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/newPartDialog.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,44 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NEWPARTDIALOG_H
+#define NEWPARTDIALOG_H
+
+#include <qdialog.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qcombobox.h>
+#include <qdialogbuttonbox.h>
+#include <qradiobutton.h>
+#include <qbuttongroup.h>
+#include "gui.h"
+#include "radiobox.h"
+
+class NewPartDialog : public QDialog {
+public:
+	explicit NewPartDialog (QWidget* parent = null, Qt::WindowFlags f = 0);
+	static void StaticDialog ();
+	
+	QLabel* lb_brickIcon, *lb_name, *lb_author, *lb_license, *lb_BFC;
+	QLineEdit* le_name, *le_author;
+	
+	RadioBox* rb_license, *rb_BFC;
+	QDialogButtonBox* bbx_buttons;
+};
+
+#endif // NEWPARTDIALOG_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/radiobox.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,97 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <qboxlayout.h>
+#include <qradiobutton.h>
+#include "radiobox.h"
+
+static QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) {
+	return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom;
+}
+
+void RadioBox::init (Qt::Orientation orient) {
+	dir = makeDirection (orient);
+	
+	buttonGroup = new QButtonGroup;
+	currentId = 0;
+	coreLayout = null;
+	
+	coreLayout = new QBoxLayout (makeDirection (orient, true));
+	setLayout (coreLayout);
+	
+	// Init the first row with a break
+	rowBreak ();
+	
+	connect (buttonGroup, SIGNAL (buttonPressed (QAbstractButton*)), this, SLOT (slot_buttonPressed (QAbstractButton*)));
+	connect (buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int)));
+}
+
+RadioBox::RadioBox (const QString& title, initlist<char const*> entries, int const defaultId,
+	const Qt::Orientation orient, QWidget* parent) : QGroupBox (title, parent), defaultId (defaultId)
+{
+	init (orient);
+	
+	for (char const* entry : entries)
+		addButton (entry);
+}
+
+void RadioBox::rowBreak () {
+	QBoxLayout* newLayout = new QBoxLayout (dir);
+	currentLayout = newLayout;
+	layouts.push_back (newLayout);
+	
+	coreLayout->addLayout (newLayout);
+}
+
+void RadioBox::addButton (const char* entry) {
+	QRadioButton* button = new QRadioButton (entry);
+	addButton (button);
+}
+
+void RadioBox::addButton (QRadioButton* button) {
+	bool const selectThis = (currentId == defaultId);
+	
+	objects.push_back (button);
+	buttonGroup->addButton (button, currentId++);
+	currentLayout->addWidget (button);
+	
+	if (selectThis)
+		button->setChecked (true);
+}
+
+RadioBox& RadioBox::operator<< (QRadioButton* button) {
+	addButton (button);
+	return *this;
+}
+
+RadioBox& RadioBox::operator<< (const char* entry) {
+	addButton (entry);
+	return *this;
+}
+
+void RadioBox::setCurrentRow (uint row) {
+	currentLayout = layouts[row];
+}
+
+void RadioBox::slot_buttonPressed (int btn) {
+	emit sig_buttonPressed (btn);
+}
+
+void RadioBox::slot_buttonPressed (QAbstractButton* btn) {
+	emit sig_buttonPressed (btn);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/radiobox.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,115 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RADIOBOX_H
+#define RADIOBOX_H
+
+#include "common.h"
+#include <qwidget.h>
+#include <QGroupBox>
+#include <qradiobutton.h>
+#include <qboxlayout.h>
+#include <qbuttongroup.h>
+
+// =============================================================================
+// RadioBox
+//
+// Convenience widget - is a groupbox of radio buttons.
+// =============================================================================
+class RadioBox : public QGroupBox {
+	Q_OBJECT
+	
+public:
+	void init (Qt::Orientation orient);
+	
+	explicit RadioBox (QWidget* parent = null) : QGroupBox (parent) {
+		init (Qt::Vertical);
+	}
+	
+	explicit RadioBox (const QString& title, QWidget* parent = null) : QGroupBox (title, parent) {
+		init (Qt::Vertical);
+	}
+	
+	explicit RadioBox () {
+		init (Qt::Vertical);
+	}
+	
+	explicit RadioBox (const QString& title, initlist<char const*> entries, int const defaultId,
+		const Qt::Orientation orient = Qt::Vertical, QWidget* parent = null);
+	
+	void rowBreak ();
+	void setCurrentRow (uint row);
+	void addButton (const char* entry);
+	void addButton (QRadioButton* button);
+	RadioBox& operator<< (QRadioButton* button);
+	RadioBox& operator<< (const char* entry);
+	
+	int value () const {
+		return buttonGroup->checkedId ();
+	}
+	
+	void setValue (int val) {
+		buttonGroup->button (val)->setChecked (true);
+	}
+	
+	std::vector<QRadioButton*>::iterator begin () {
+		return objects.begin ();
+	}
+	
+	std::vector<QRadioButton*>::iterator end () {
+		return objects.end ();
+	}
+	
+	QRadioButton* operator[] (uint n) const {
+		return objects[n];
+	}
+	
+	bool exclusive () const {
+		return buttonGroup->exclusive ();
+	}
+	
+	void setExclusive (bool val) {
+		buttonGroup->setExclusive (val);
+	}
+	
+	bool isChecked (int n) const {
+		return buttonGroup->checkedId () == n;
+	}
+
+signals:
+	void sig_buttonPressed (int btn);
+	void sig_buttonPressed (QAbstractButton* btn);
+
+private:
+	std::vector<QRadioButton*> objects;
+	std::vector<QBoxLayout*> layouts;
+	QBoxLayout* coreLayout;
+	QBoxLayout* currentLayout;
+	QBoxLayout::Direction dir;
+	int currentId;
+	int defaultId;
+	QButtonGroup* buttonGroup;
+	
+	Q_DISABLE_COPY (RadioBox)
+
+private slots:
+	void slot_buttonPressed (int btn);
+	void slot_buttonPressed (QAbstractButton* btn);
+};
+
+#endif // RADIOBOX_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/setContentsDialog.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,107 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QAbstractButton>
+#include <qboxlayout.h>
+#include "setContentsDialog.h"
+#include "file.h"
+#include "gui.h"
+#include "history.h"
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+SetContentsDialog::SetContentsDialog (LDObject* obj, QWidget* parent) : QDialog(parent) {
+	setWindowTitle (APPNAME ": Set Contents");
+	
+	lb_contents = new QLabel ("Set contents:", parent);
+	
+	le_contents = new QLineEdit (parent);
+	le_contents->setText (obj->getContents ().chars());
+	le_contents->setWhatsThis ("The LDraw code of this object. The code written "
+		"here is expected to be valid LDraw code, invalid code here results "
+		"the object being turned into an error object. Please do refer to the "
+		"<a href=\"http://www.ldraw.org/article/218.html\">official file format "
+		"standard</a> for further information.");
+	le_contents->setMinimumWidth (384);
+	
+	if (obj->getType() == LDObject::Gibberish) {
+		lb_error = new QLabel;
+		lb_error->setText (fmt ("<span style=\"color: #900\">%s</span>",
+			static_cast<LDGibberish*> (obj)->zReason.chars()));
+		
+		QPixmap qErrorPixmap = getIcon ("error").scaledToHeight (16);
+		
+		lb_errorIcon = new QLabel;
+		lb_errorIcon->setPixmap (qErrorPixmap);
+	}
+	
+	IMPLEMENT_DIALOG_BUTTONS
+	
+	QVBoxLayout* layout = new QVBoxLayout;
+	layout->addWidget (lb_contents);
+	layout->addWidget (le_contents);
+	
+	QHBoxLayout* layout2 = new QHBoxLayout;
+	
+	if (obj->getType() == LDObject::Gibberish) {
+		layout2->addWidget (lb_errorIcon);
+		layout2->addWidget (lb_error);
+	}
+	
+	layout2->addWidget (bbx_buttons);
+	layout->addLayout (layout2);
+	setLayout (layout);
+	
+	setWindowTitle (APPNAME ": Set Contents");
+	setWindowIcon (getIcon ("set-contents"));
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void SetContentsDialog::slot_handleButtons (QAbstractButton* qButton) {
+	qButton = qButton;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void SetContentsDialog::staticDialog (LDObject* obj) {
+	if (!obj)
+		return;
+	
+	SetContentsDialog dlg (obj, g_win);
+	if (dlg.exec () == false)
+		return;
+	
+	LDObject* oldobj = obj;
+	
+	// Reinterpret it from the text of the input field
+	obj = parseLine (dlg.le_contents->text ().toStdString ().c_str ());
+	
+	// Mark down the history now before we perform the replacement (which
+	// destroys the old object)
+	History::addEntry (new EditHistory ({(ulong) oldobj->getIndex (g_curfile)},
+		{oldobj->clone ()}, {obj->clone ()}));
+	
+	oldobj->replace (obj);
+	
+	// Rebuild stuff after this
+	g_win->refresh ();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/setContentsDialog.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,47 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SETCONTENTSDIALOG_H
+
+#include <qdialog.h>
+#include <qlineedit.h>
+#include <qlabel.h>
+#include <qdialogbuttonbox.h>
+#include "common.h"
+
+// =============================================================================
+// SetContentsDialog
+//
+// Performs the Set Contents dialog on the given LDObject. Object's contents
+// are exposed to the user and is reinterpreted if the user accepts the new
+// contents.
+// =============================================================================
+class SetContentsDialog : public QDialog {
+public:
+	QLabel* lb_contents, *lb_errorIcon, *lb_error;
+	QLineEdit* le_contents;
+	QDialogButtonBox* bbx_buttons;
+	
+	SetContentsDialog (LDObject* obj, QWidget* parent = null);
+	static void staticDialog (LDObject* obj);
+	
+private slots:
+	void slot_handleButtons (QAbstractButton* qButton);
+};
+
+#endif // SETCONTENTSDIALOG_H
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/str.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,503 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+// #include <initializer_list>
+#include "str.h"
+#include "common.h"
+#include "misc.h"
+
+#define ITERATE_STRING(u) \
+	for (unsigned int u = 0; u < strlen (text); u++)
+
+// ============================================================================
+// vdynformat: Try to write to a formatted string with size bytes first, if
+// that fails, double the size and keep recursing until it works.
+char* vdynformat (const char* csFormat, va_list vArgs, long lSize) {
+	char* buffer = new char[lSize];
+	int r = vsnprintf (buffer, lSize - 1, csFormat, vArgs);
+	if (r > (signed)(lSize - 1) || r < 0) {
+		delete[] buffer;
+		buffer = vdynformat (csFormat, vArgs, lSize * 2);
+	}
+	return buffer;
+}
+
+// ============================================================================
+str::str () {
+	text = new char[1];
+	clear();
+	alloclen = strlen (text);
+}
+
+str::str (const char* c) {
+	text = new char[1];
+	text[0] = '\0';
+	curs = alloclen = 0;
+	append (c);
+}
+
+str::str (char c) {
+	text = new char[1];
+	text[0] = '\0';
+	curs = alloclen = 0;
+	append (c);
+}
+
+str::str (QString c) {
+	text = new char[1];
+	text[0] = '\0';
+	curs = alloclen = 0;
+	append (c);
+}
+
+str::~str () {
+	// delete[] text;
+}
+
+// ============================================================================
+void str::clear () {
+	delete[] text;
+	text = new char[1];
+	text[0] = '\0';
+	curs = 0;
+	alloclen = 0;
+}
+
+// ============================================================================
+void str::resize (unsigned int len) {
+	unsigned int oldlen = strlen (text);
+	char* oldtext = new char[oldlen];
+	strncpy (oldtext, text, oldlen);
+	
+	delete[] text;
+	text = new char[len+1];
+	for (unsigned int u = 0; u < len+1; u++)
+		text[u] = 0;
+	strncpy (text, oldtext, len);
+	delete[] oldtext;
+	
+	alloclen = len;
+}
+
+// ============================================================================
+void str::dump () {
+	for (unsigned int u = 0; u <= alloclen; u++)
+		printf ("\t%u. %u (%c)\n", u, text[u], text[u]);
+}
+
+// ============================================================================
+// Adds a new character at the end of the string.
+void str::append (const char c) {
+	// Out of space, thus resize
+	if (curs == alloclen)
+		resize (alloclen + 1);
+	text[curs] = c;
+	curs++;
+}
+
+void str::append (const char* c) {
+	resize (alloclen + strlen (c));
+	
+	for (unsigned int u = 0; u < strlen (c); u++) {
+		if (c[u] != 0)
+			append (c[u]);
+	}
+}
+
+void str::append (str c) {
+	append (c.chars());
+}
+
+void str::append (QString c) {
+	append (c.toUtf8 ().constData ());
+}
+
+// ============================================================================
+void str::appendformat (const char* c, ...) {
+	va_list v;
+	
+	va_start (v, c);
+	char* buf = vdynformat (c, v, 256);
+	va_end (v);
+	
+	append (buf);
+	delete[] buf;
+}
+
+void str::format (const char* fmt, ...) {
+	clear ();
+	
+	va_list v;
+	
+	va_start (v, fmt);
+	char* buf = vdynformat (fmt, v, 256);
+	va_end (v);
+	
+	append (buf);
+	delete[] buf;
+}
+
+// ============================================================================
+char* str::chars () {
+	return text;
+}
+
+// ============================================================================
+int str::first (const char* c, unsigned int a) {
+	unsigned int r = 0;
+	unsigned int index = 0;
+	for (; a < alloclen; a++) {
+		if (text[a] == c[r]) {
+			if (r == 0)
+				index = a;
+			
+			r++;
+			if (r == strlen (c))
+				return index;
+		} else {
+			if (r != 0) {
+				// If the string sequence broke at this point, we need to
+				// check this character again, for a new sequence just
+				// might start right here.
+				a--;
+			}
+			
+			r = 0;
+		}
+	}
+	
+	return -1;
+}
+
+// ============================================================================
+int str::last (const char* c, int a) {
+	if (a == -1)
+		a = len();
+	
+	int max = strlen (c)-1;
+	
+	int r = max;
+	for (; a >= 0; a--) {
+		if (text[a] == c[r]) {
+			r--;
+			if (r == -1)
+				return a;
+		} else {
+			if (r != max)
+				a++;
+			
+			r = max;
+		}
+	}
+	
+	return -1;
+}
+
+// ============================================================================
+str str::substr (unsigned int a, unsigned int b) {
+	if (a > len()) a = len();
+	if (b > len()) b = len();
+	
+	if (b == a)
+		return "";
+	
+	if (b < a) {
+		printf ("str::substring:: indices %u and %u given, should be the other way around, swapping..\n", a, b);
+		
+		// Swap the variables
+		unsigned int c = a;
+		a = b;
+		b = c;
+	}
+	
+	char* s = new char[b - a + 1];
+	strncpy (s, text + a, b - a);
+	s[b - a] = '\0';
+	
+	str other = s;
+	delete[] s;
+	return other;
+}
+
+// ============================================================================
+void str::remove (unsigned int idx, unsigned int dellen) {
+	str s1 = substr (0, idx);
+	str s2 = substr (idx + dellen, -1);
+	
+	clear();
+	
+	append (s1);
+	append (s2);
+}
+
+// ============================================================================
+str str::trim (int dellen) {
+	if (dellen > 0)
+		return substr (0, len() - dellen);
+	return substr (-dellen, len());
+}
+
+// ============================================================================
+void str::replace (const char* o, const char* n, unsigned int a) {
+	for (int idx; (idx = first (o, a)) != -1;) {
+		str s1 = substr (0, idx);
+		str s2 = substr (idx + strlen (o), len());
+		
+		clear();
+		
+		append (s1);
+		append (n);
+		append (s2);
+	}
+}
+
+// ============================================================================
+str str::strip (char c) {
+	return strip ({c});
+}
+
+str str::strip (std::initializer_list<char> unwanted) {
+	str cache = text;
+	uint oldlen = len();
+	
+	char* buf = new char[oldlen];
+	char* bufptr = buf;
+	for (uint i = 0; i < oldlen; i++) {
+		bool valid = true;
+		for (const char* j = unwanted.begin(); j < unwanted.end() && valid; j++)
+			if (text[i] == *j)
+				valid = false;
+		
+		if (valid)
+			*bufptr++ = text[i];
+	}
+	
+	*bufptr = '\0';
+	assert (bufptr <= buf + oldlen);
+	
+	str zResult = buf;
+	delete[] buf;
+	
+	return zResult;
+}
+
+void str::insert (char* c, unsigned int pos) {
+	str s1 = substr (0, pos);
+	str s2 = substr (pos, len());
+	
+	clear();
+	append (s1);
+	append (c);
+	append (s2);
+}
+
+str str::reverse () {
+	char* buf = new char[len() + 1];
+	
+	for (uint i = 0; i < len(); i++)
+		buf[i] = text[len() - i - 1];
+	buf[len()] = '\0';
+	
+	str other = buf;
+	delete[] buf;
+	return other;
+}
+
+str str::repeat (int n) {
+	assert (n >= 0);
+	
+	str other;
+	for (int i = 0; i < n; i++)
+		other += text;
+	return other;
+}
+
+// ============================================================================
+bool str::isnumber () {
+	ITERATE_STRING (u) {
+		// Minus sign as the first character is allowed for negatives
+		if (!u && text[u] == '-')
+			continue;
+		
+		if (text[u] < '0' || text[u] > '9')
+			return false;
+	}
+	return true;
+}
+
+// ============================================================================
+bool str::isword () {
+	ITERATE_STRING (u) {
+		// lowercase letters
+		if (text[u] >= 'a' || text[u] <= 'z')
+			continue;
+		
+		// uppercase letters
+		if (text[u] >= 'A' || text[u] <= 'Z')
+			continue;
+		
+		return false;
+	}
+	return true;
+}
+
+int str::instanceof (const char* c, uint n) {
+	unsigned int r = 0;
+	unsigned int index = 0;
+	unsigned int x = 0;
+	for (uint a = 0; a < alloclen; a++) {
+		if (text[a] == c[r]) {
+			if (r == 0)
+				index = a;
+			
+			r++;
+			if (r == strlen (c)) {
+				if (x++ == n)
+					return index;
+				r = 0;
+			}
+		} else {
+			if (r != 0)
+				a--;
+			r = 0;
+		}
+	}
+	
+	return -1;
+}
+
+// ============================================================================
+int str::compare (const char* c) {
+	return strcmp (text, c);
+}
+
+int str::compare (str c) {
+	return compare (c.chars());
+}
+
+int str::icompare (const char* c) {
+	return icompare (str ((char*)c));
+}
+
+int str::icompare (str b) {
+	return strcmp (tolower().chars(), b.tolower().chars());
+}
+
+// ============================================================================
+str str::tolower () {
+	str n = text;
+	
+	for (uint u = 0; u < len(); u++) {
+		if (n[u] >= 'A' && n[u] < 'Z')
+			n.text[u] += ('a' - 'A');
+	}
+	
+	return n;
+}
+
+// ============================================================================
+str str::toupper () {
+	str n = text;
+	
+	for (uint u = 0; u < len(); u++) {
+		if (n[u] >= 'a' && n[u] < 'z')
+			n.text[u] -= ('a' - 'A');
+	}
+	
+	return n;
+}
+
+// ============================================================================
+unsigned str::count (char c) {
+	unsigned n = 0;
+	ITERATE_STRING (u)
+		if (text[u] == c)
+			n++;
+	return n;
+}
+
+unsigned str::count (char* c) {
+	unsigned int r = 0;
+	unsigned int tmp = 0;
+	ITERATE_STRING (u) {
+		if (text[u] == c[r]) {
+			r++;
+			if (r == strlen (c)) {
+				r = 0;
+				tmp++;
+			}
+		} else {
+			if (r != 0)
+				u--;
+			r = 0;
+		}
+	}
+	
+	return tmp;
+}
+
+// ============================================================================
+std::vector<str> str::split (str del, bool bNoBlanks) {
+	std::vector<str> res;
+	unsigned int a = 0;
+	
+	// Find all separators and store the text left to them.
+	while (1) {
+		int b = first (del, a);
+		
+		if (b == -1)
+			break;
+		
+		if (!bNoBlanks || (b - a))
+			res.push_back (substr (a, b));
+		
+		a = b + strlen (del);
+	}
+	
+	// Add the string at the right of the last separator
+	if (!bNoBlanks || (len () - a))
+		res.push_back (substr (a, len ()));
+	return res;
+}
+
+std::vector<str> str::operator/ (str splitstring) {return split(splitstring);}
+std::vector<str> str::operator/ (char* splitstring) {return split(splitstring);}
+std::vector<str> str::operator/ (const char* splitstring) {return split(splitstring);}
+
+str& str::operator+= (vertex vrt) {
+	appendformat ("%s", vrt.stringRep (false).chars());
+	return *this;
+}
+
+str fmt (const char* fmt, ...) {
+	va_list va;
+	char* buf;
+	
+	va_start (va, fmt);
+	buf = vdynformat (fmt, va, 256);
+	va_end (va);
+	
+	str val = buf;
+	delete[] buf;
+	return val;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/str.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,271 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef STR_H
+#define STR_H
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <vector>
+#include <QString>
+
+char* vdynformat (const char* csFormat, va_list vArgs, long int lSize);
+
+class vertex;
+
+// Dynamic string object, allocates memory when needed and
+// features a good bunch of manipulation methods
+class str {
+private:
+	// The actual message
+	char* text;
+	
+	// Where will append() place new characters?
+	unsigned short curs;
+	
+	// Allocated length
+	unsigned short alloclen;
+	
+	// Resize the text buffer to len characters
+	void resize (unsigned int len);
+	
+public:
+	// ======================================================================
+	str ();
+	str (const char* c);
+	str (char c);
+	str (QString c);
+	~str ();
+	
+	// ======================================================================
+	// METHODS
+	
+	// Empty the string
+	void clear ();
+	
+	// Length of the string
+	size_t len () {
+		return strlen (text);
+	}
+	
+	// The char* form of the string
+	char* chars ();
+	
+	// Dumps the character table of the string
+	void dump ();
+	
+	// Appends text to the string
+	void append (const char c);
+	void append (const char* c);
+	void append (str c);
+	void append (QString c);
+	
+	// Formats text to the string.
+	void format (const char* fmt, ...);
+	
+	// Appends formatted text to the string.
+	void appendformat (const char* c, ...);
+	
+	// Returns the first occurrence of c in the string, optionally starting
+	// from a certain position rather than the start.
+	int first (const char* c, unsigned int a = 0);
+	
+	// Returns the last occurrence of c in the string, optionally starting
+	// from a certain position rather than the end.
+	int last (const char* c, int a = -1);
+	
+	// Returns a substring of the string, from a to b.
+	str substr (unsigned int a, unsigned int b);
+	
+	// Replace a substring with another substring.
+	void replace (const char* o, const char* n, unsigned int a = 0);
+	
+	// Removes a given index from the string, optionally more characters than just 1.
+	void remove (unsigned int idx, unsigned int dellen=1);
+	
+	// Trims the given amount of characters. If negative, the characters
+	// are removed from the beginning of the string, if positive, from the
+	// end of the string.
+	str trim (int dellen);
+	
+	// Inserts a substring into a certain position.
+	void insert (char* c, unsigned int pos);
+	
+	// Reverses the string.
+	str reverse ();
+	
+	// Repeats the string a given amount of times.
+	str repeat (int n);
+	
+	// Is the string a number?
+	bool isnumber ();
+	
+	// Is the string a word, i.e consists only of alphabetic letters?
+	bool isword ();
+	
+	// Convert string to lower case
+	str tolower ();
+	
+	// Convert string to upper case
+	str toupper ();
+	
+	// Compare this string with another
+	int compare (const char* c);
+	int compare (str c);
+	int icompare (str c);
+	int icompare (const char* c);
+	
+	// Counts the amount of substrings in the string
+	unsigned int count (char* s);
+	unsigned int count (char s);
+	
+	// Counts where the given substring is seen for the nth time
+	int instanceof (const char* s, unsigned n);
+	
+	char subscript (uint pos) {
+		return operator[] (pos);
+	}
+	
+	std::vector<str> split (str del, bool bNoBlanks = false);
+	
+	str strip (char c);
+	str strip (std::initializer_list<char> unwanted);
+	
+	// ======================================================================
+	// OPERATORS
+	str operator+ (str& c) {
+		append (c);
+		return *this;
+	}
+	
+	str& operator+= (char c) {
+		append (c);
+		return *this;
+	}
+	
+	str& operator+= (const char* c) {
+		append (c);
+		return *this;
+	}
+	
+	str& operator+= (const str c) {
+		append (c);
+		return *this;
+	}
+	
+	str& operator+= (const QString c) {
+		append (c);
+		return *this;
+	}
+	
+	str& operator+= (vertex vrt);
+	
+	str operator* (const int repcount) {
+		repeat (repcount);
+		return *this;
+	}
+	
+	str& operator*= (const int repcount) {
+		str other = repeat (repcount);
+		clear ();
+		append (other);
+		return *this;
+	}
+	
+	str operator- (const int trimcount) {
+		return trim (trimcount);
+	}
+	
+	str& operator-= (const int trimcount) {
+		str other = trim (trimcount);
+		clear ();
+		append (other);
+		return *this;
+	}
+	
+	std::vector<str> operator/ (str splitstring);
+	std::vector<str> operator/ (char* splitstring);
+	std::vector<str> operator/ (const char* splitstring);
+	
+	int operator% (str splitstring) {
+		return count (splitstring.chars());
+	}
+	
+	int operator% (char* splitstring) {
+		return count (splitstring);
+	}
+	
+	int operator% (const char* splitstring) {
+		return count (str (splitstring).chars());
+	}
+	
+	str operator+ () {
+		return toupper ();
+	}
+	
+	str operator- () {
+		return tolower ();
+	}
+	
+	str operator! () {
+		return reverse ();
+	}
+	
+	size_t operator~ () {
+		return len ();
+	}
+	
+#define DEFINE_OPERATOR_TYPE(OPER, TYPE) \
+	bool operator OPER (TYPE other) {return compare(other) OPER 0;}
+#define DEFINE_OPERATOR(OPER) \
+	DEFINE_OPERATOR_TYPE (OPER, str) \
+	DEFINE_OPERATOR_TYPE (OPER, char*) \
+	DEFINE_OPERATOR_TYPE (OPER, const char*)
+	
+	DEFINE_OPERATOR (==)
+	DEFINE_OPERATOR (!=)
+	DEFINE_OPERATOR (>)
+	DEFINE_OPERATOR (<)
+	DEFINE_OPERATOR (>=)
+	DEFINE_OPERATOR (<=)
+	
+	char& operator[] (int pos) {
+		return text[pos];
+	}
+	
+	operator char* () const {
+		return text;
+	}
+	
+	operator QString () const {
+		return text;
+	}
+	
+	operator int () const {
+		return atoi (text);
+	}
+	
+	operator uint () const {
+		return operator int();
+	}
+};
+
+str fmt (const char* fmt, ...);
+
+#endif // STR_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/types.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,53 @@
+#include <assert.h>
+#include "common.h"
+#include "types.h"
+#include "misc.h"
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+vertex vertex::midpoint (vertex& other) {
+	vertex mid;
+	
+	for (const Axis ax : g_Axes)
+		mid[ax] = (m_coords[ax] + other[ax]) / 2;
+	
+	return mid;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+str vertex::stringRep (const bool mangled) {
+	return fmt (mangled ? "(%s, %s, %s)" : "%s %s %s",
+		ftoa (coord (X)).chars(),
+		ftoa (coord (Y)).chars(),
+		ftoa (coord (Z)).chars());
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void vertex::transform (matrix<3> matr, vertex pos) {
+	double x2 = (matr[0] * x ()) + (matr[1] * y ()) + (matr[2] * z ()) + pos[X];
+	double y2 = (matr[3] * x ()) + (matr[4] * y ()) + (matr[5] * z ()) + pos[Y];
+	double z2 = (matr[6] * x ()) + (matr[7] * y ()) + (matr[8] * z ()) + pos[Z];
+	
+	x () = x2;
+	y () = y2;
+	z () = z2;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void vertex::transform (matrix<4> matr) {
+	double x2 = (matr[0] * x ()) + (matr[4] + y ()) + (matr[8] + z ()) + matr[12];
+	double y2 = (matr[1] * x ()) + (matr[5] + y ()) + (matr[9] + z ()) + matr[13];
+	double z2 = (matr[2] * x ()) + (matr[6] + y ()) + (matr[10] + z ()) + matr[14];
+	double w2 = (matr[3] * x ()) + (matr[7] + y ()) + (matr[11] + z ()) + matr[15];
+	
+	x () = x2 / w2;
+	y () = y2 / w2;
+	z () = z2 / w2;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/types.h	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,222 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 Santeri Piippo
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TYPES_H
+#define TYPES_H
+
+#include "common.h"
+#include "misc.h"
+
+typedef unsigned int uint;
+typedef short unsigned int ushort;
+typedef long unsigned int ulong;
+
+// Typedef out the _t suffices :)
+typedef int8_t int8;
+typedef int16_t int16;
+typedef int32_t int32;
+typedef int64_t int64;
+typedef uint8_t uint8;
+typedef uint16_t uint16;
+typedef uint32_t uint32;
+typedef uint64_t uint64;
+
+template<class T> using initlist = std::initializer_list<T>;
+using std::vector;
+
+enum Axis { X, Y, Z };
+static const Axis g_Axes[3] = {X, Y, Z};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+// matrix
+// 
+// A templated, mathematical N x N matrix
+// =============================================================================
+template<int N> class matrix {
+public:
+	// Constructors
+	matrix () {}
+	matrix (initlist<double> vals) {
+		assert (vals.size() == N * N);
+		memcpy (&m_vals[0], &(*vals.begin ()), sizeof m_vals);
+	}
+	
+	matrix (double fillval) {
+		for (short i = 0; i < (N * N); ++i)
+			m_vals[i] = fillval;
+	}
+	
+	matrix (double vals[]) {
+		for (short i = 0; i < (N * N); ++i)
+			m_vals[i] = vals[i];
+	}
+	
+	template<int M> matrix (matrix<M> other) {
+		assert (M >= N);
+		
+		for (short i = 0; i < M; ++i)
+		for (short j = 0; j < M; ++j) {
+			const short idx = (i * M) + j;
+			m_vals[idx] = other[idx];
+		}
+	}
+	
+	matrix<N> mult (matrix<N> other) {
+		matrix val;
+		val.zero ();
+
+		for (short i = 0; i < N; ++i)
+		for (short j = 0; j < N; ++j)
+		for (short k = 0; k < N; ++k)
+			val[(i * N) + j] += m_vals[(i * N) + k] * other[(k * N) + j];
+
+		return val;
+	}
+	
+	matrix<N>& operator= (matrix<N> other) {
+		memcpy (&m_vals[0], &other.m_vals[0], sizeof (double) * N * N);
+		return *this;
+	}
+	
+	matrix<N> operator* (matrix<N> other) {
+		return mult (other);
+	}
+	
+	double& operator[] (const uint idx) {
+		return m_vals[idx];
+	}
+	
+	const double& operator[] (const uint idx) const {
+		return m_vals[idx];
+	}
+	
+	void zero () {
+		memset (&m_vals[0], 0, sizeof (double) * N * N);
+	}
+	
+	void puts () const {
+		for (short i = 0; i < N; ++i) {
+			for (short j = 0; j < N; ++j)
+				printf ("%*f\t", 10, m_vals[(i * N) + j]);
+			
+			printf ("\n");
+		}
+	}
+	
+	str stringRep () const {
+		str val;
+		for (short i = 0; i < N * N; ++i) {
+			if (i > 0)
+				val += ' ';
+			
+			val.appendformat ("%s", ftoa (m_vals[i]).chars());
+		}
+		
+		return val;	
+	}
+
+private:
+	double m_vals[N * N];
+};
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+// vertex
+// 
+// Vertex class, contains a single point in 3D space. Not to be confused with
+// LDVertex, which is a vertex used in an LDraw part file.
+// =============================================================================
+class vertex {
+public:
+	double m_coords[3];
+	
+	vertex () {}
+	vertex (double x, double y, double z) {
+		m_coords[X] = x;
+		m_coords[Y] = y;
+		m_coords[Z] = z;
+	}
+	
+	void move (vertex other) {
+		for (const Axis ax : g_Axes)
+			m_coords[ax] += other[ax];
+	}
+	
+	vertex& operator+= (vertex other) {
+		move (other);
+		return *this;
+	}
+	
+	vertex operator/ (const double d) const {
+		vertex other (*this);
+		return other /= d;
+	}
+	
+	vertex& operator/= (const double d) {
+		for (const Axis ax : g_Axes)
+			m_coords[ax] /= d;
+		return *this;
+	}
+	
+	bool operator== (const vertex& other) const {
+		return coord (X) == other[X] &&
+			coord (Y) == other[Y] &&
+			coord (Z) == other[Z];
+	}
+	
+	bool operator!= (const vertex& other) const {
+		return !operator== (other);
+	}
+	
+	vertex operator- () const {
+		return vertex (-m_coords[X], -m_coords[Y], -m_coords[Z]);
+	}
+	
+	double& coord (const ushort n) {
+		return m_coords[n];
+	}
+	
+	const double& coord (const ushort n) const {
+		return m_coords[n];
+	}
+	
+	double& operator[] (const Axis ax) {
+		return coord ((ushort) ax);
+	}
+	
+	const double& operator[] (const Axis ax) const {
+		return coord ((ushort) ax);
+	}
+	
+	double& x () { return m_coords[X]; }
+	double& y () { return m_coords[Y]; }
+	double& z () { return m_coords[Z]; }
+	const double& x () const { return m_coords[X]; }
+	const double& y () const { return m_coords[Y]; }
+	const double& z () const { return m_coords[Z]; }
+	
+	vertex midpoint (vertex& other);
+	str stringRep (const bool mangled);
+	void transform (matrix<3> matr, vertex pos);
+	void transform (matrix<4> matr);
+};
+
+#endif // TYPES_H
\ No newline at end of file
--- a/str.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,503 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <assert.h>
-// #include <initializer_list>
-#include "str.h"
-#include "common.h"
-#include "misc.h"
-
-#define ITERATE_STRING(u) \
-	for (unsigned int u = 0; u < strlen (text); u++)
-
-// ============================================================================
-// vdynformat: Try to write to a formatted string with size bytes first, if
-// that fails, double the size and keep recursing until it works.
-char* vdynformat (const char* csFormat, va_list vArgs, long lSize) {
-	char* buffer = new char[lSize];
-	int r = vsnprintf (buffer, lSize - 1, csFormat, vArgs);
-	if (r > (signed)(lSize - 1) || r < 0) {
-		delete[] buffer;
-		buffer = vdynformat (csFormat, vArgs, lSize * 2);
-	}
-	return buffer;
-}
-
-// ============================================================================
-str::str () {
-	text = new char[1];
-	clear();
-	alloclen = strlen (text);
-}
-
-str::str (const char* c) {
-	text = new char[1];
-	text[0] = '\0';
-	curs = alloclen = 0;
-	append (c);
-}
-
-str::str (char c) {
-	text = new char[1];
-	text[0] = '\0';
-	curs = alloclen = 0;
-	append (c);
-}
-
-str::str (QString c) {
-	text = new char[1];
-	text[0] = '\0';
-	curs = alloclen = 0;
-	append (c);
-}
-
-str::~str () {
-	// delete[] text;
-}
-
-// ============================================================================
-void str::clear () {
-	delete[] text;
-	text = new char[1];
-	text[0] = '\0';
-	curs = 0;
-	alloclen = 0;
-}
-
-// ============================================================================
-void str::resize (unsigned int len) {
-	unsigned int oldlen = strlen (text);
-	char* oldtext = new char[oldlen];
-	strncpy (oldtext, text, oldlen);
-	
-	delete[] text;
-	text = new char[len+1];
-	for (unsigned int u = 0; u < len+1; u++)
-		text[u] = 0;
-	strncpy (text, oldtext, len);
-	delete[] oldtext;
-	
-	alloclen = len;
-}
-
-// ============================================================================
-void str::dump () {
-	for (unsigned int u = 0; u <= alloclen; u++)
-		printf ("\t%u. %u (%c)\n", u, text[u], text[u]);
-}
-
-// ============================================================================
-// Adds a new character at the end of the string.
-void str::append (const char c) {
-	// Out of space, thus resize
-	if (curs == alloclen)
-		resize (alloclen + 1);
-	text[curs] = c;
-	curs++;
-}
-
-void str::append (const char* c) {
-	resize (alloclen + strlen (c));
-	
-	for (unsigned int u = 0; u < strlen (c); u++) {
-		if (c[u] != 0)
-			append (c[u]);
-	}
-}
-
-void str::append (str c) {
-	append (c.chars());
-}
-
-void str::append (QString c) {
-	append (c.toUtf8 ().constData ());
-}
-
-// ============================================================================
-void str::appendformat (const char* c, ...) {
-	va_list v;
-	
-	va_start (v, c);
-	char* buf = vdynformat (c, v, 256);
-	va_end (v);
-	
-	append (buf);
-	delete[] buf;
-}
-
-void str::format (const char* fmt, ...) {
-	clear ();
-	
-	va_list v;
-	
-	va_start (v, fmt);
-	char* buf = vdynformat (fmt, v, 256);
-	va_end (v);
-	
-	append (buf);
-	delete[] buf;
-}
-
-// ============================================================================
-char* str::chars () {
-	return text;
-}
-
-// ============================================================================
-int str::first (const char* c, unsigned int a) {
-	unsigned int r = 0;
-	unsigned int index = 0;
-	for (; a < alloclen; a++) {
-		if (text[a] == c[r]) {
-			if (r == 0)
-				index = a;
-			
-			r++;
-			if (r == strlen (c))
-				return index;
-		} else {
-			if (r != 0) {
-				// If the string sequence broke at this point, we need to
-				// check this character again, for a new sequence just
-				// might start right here.
-				a--;
-			}
-			
-			r = 0;
-		}
-	}
-	
-	return -1;
-}
-
-// ============================================================================
-int str::last (const char* c, int a) {
-	if (a == -1)
-		a = len();
-	
-	int max = strlen (c)-1;
-	
-	int r = max;
-	for (; a >= 0; a--) {
-		if (text[a] == c[r]) {
-			r--;
-			if (r == -1)
-				return a;
-		} else {
-			if (r != max)
-				a++;
-			
-			r = max;
-		}
-	}
-	
-	return -1;
-}
-
-// ============================================================================
-str str::substr (unsigned int a, unsigned int b) {
-	if (a > len()) a = len();
-	if (b > len()) b = len();
-	
-	if (b == a)
-		return "";
-	
-	if (b < a) {
-		printf ("str::substring:: indices %u and %u given, should be the other way around, swapping..\n", a, b);
-		
-		// Swap the variables
-		unsigned int c = a;
-		a = b;
-		b = c;
-	}
-	
-	char* s = new char[b - a + 1];
-	strncpy (s, text + a, b - a);
-	s[b - a] = '\0';
-	
-	str other = s;
-	delete[] s;
-	return other;
-}
-
-// ============================================================================
-void str::remove (unsigned int idx, unsigned int dellen) {
-	str s1 = substr (0, idx);
-	str s2 = substr (idx + dellen, -1);
-	
-	clear();
-	
-	append (s1);
-	append (s2);
-}
-
-// ============================================================================
-str str::trim (int dellen) {
-	if (dellen > 0)
-		return substr (0, len() - dellen);
-	return substr (-dellen, len());
-}
-
-// ============================================================================
-void str::replace (const char* o, const char* n, unsigned int a) {
-	for (int idx; (idx = first (o, a)) != -1;) {
-		str s1 = substr (0, idx);
-		str s2 = substr (idx + strlen (o), len());
-		
-		clear();
-		
-		append (s1);
-		append (n);
-		append (s2);
-	}
-}
-
-// ============================================================================
-str str::strip (char c) {
-	return strip ({c});
-}
-
-str str::strip (std::initializer_list<char> unwanted) {
-	str cache = text;
-	uint oldlen = len();
-	
-	char* buf = new char[oldlen];
-	char* bufptr = buf;
-	for (uint i = 0; i < oldlen; i++) {
-		bool valid = true;
-		for (const char* j = unwanted.begin(); j < unwanted.end() && valid; j++)
-			if (text[i] == *j)
-				valid = false;
-		
-		if (valid)
-			*bufptr++ = text[i];
-	}
-	
-	*bufptr = '\0';
-	assert (bufptr <= buf + oldlen);
-	
-	str zResult = buf;
-	delete[] buf;
-	
-	return zResult;
-}
-
-void str::insert (char* c, unsigned int pos) {
-	str s1 = substr (0, pos);
-	str s2 = substr (pos, len());
-	
-	clear();
-	append (s1);
-	append (c);
-	append (s2);
-}
-
-str str::reverse () {
-	char* buf = new char[len() + 1];
-	
-	for (uint i = 0; i < len(); i++)
-		buf[i] = text[len() - i - 1];
-	buf[len()] = '\0';
-	
-	str other = buf;
-	delete[] buf;
-	return other;
-}
-
-str str::repeat (int n) {
-	assert (n >= 0);
-	
-	str other;
-	for (int i = 0; i < n; i++)
-		other += text;
-	return other;
-}
-
-// ============================================================================
-bool str::isnumber () {
-	ITERATE_STRING (u) {
-		// Minus sign as the first character is allowed for negatives
-		if (!u && text[u] == '-')
-			continue;
-		
-		if (text[u] < '0' || text[u] > '9')
-			return false;
-	}
-	return true;
-}
-
-// ============================================================================
-bool str::isword () {
-	ITERATE_STRING (u) {
-		// lowercase letters
-		if (text[u] >= 'a' || text[u] <= 'z')
-			continue;
-		
-		// uppercase letters
-		if (text[u] >= 'A' || text[u] <= 'Z')
-			continue;
-		
-		return false;
-	}
-	return true;
-}
-
-int str::instanceof (const char* c, uint n) {
-	unsigned int r = 0;
-	unsigned int index = 0;
-	unsigned int x = 0;
-	for (uint a = 0; a < alloclen; a++) {
-		if (text[a] == c[r]) {
-			if (r == 0)
-				index = a;
-			
-			r++;
-			if (r == strlen (c)) {
-				if (x++ == n)
-					return index;
-				r = 0;
-			}
-		} else {
-			if (r != 0)
-				a--;
-			r = 0;
-		}
-	}
-	
-	return -1;
-}
-
-// ============================================================================
-int str::compare (const char* c) {
-	return strcmp (text, c);
-}
-
-int str::compare (str c) {
-	return compare (c.chars());
-}
-
-int str::icompare (const char* c) {
-	return icompare (str ((char*)c));
-}
-
-int str::icompare (str b) {
-	return strcmp (tolower().chars(), b.tolower().chars());
-}
-
-// ============================================================================
-str str::tolower () {
-	str n = text;
-	
-	for (uint u = 0; u < len(); u++) {
-		if (n[u] >= 'A' && n[u] < 'Z')
-			n.text[u] += ('a' - 'A');
-	}
-	
-	return n;
-}
-
-// ============================================================================
-str str::toupper () {
-	str n = text;
-	
-	for (uint u = 0; u < len(); u++) {
-		if (n[u] >= 'a' && n[u] < 'z')
-			n.text[u] -= ('a' - 'A');
-	}
-	
-	return n;
-}
-
-// ============================================================================
-unsigned str::count (char c) {
-	unsigned n = 0;
-	ITERATE_STRING (u)
-		if (text[u] == c)
-			n++;
-	return n;
-}
-
-unsigned str::count (char* c) {
-	unsigned int r = 0;
-	unsigned int tmp = 0;
-	ITERATE_STRING (u) {
-		if (text[u] == c[r]) {
-			r++;
-			if (r == strlen (c)) {
-				r = 0;
-				tmp++;
-			}
-		} else {
-			if (r != 0)
-				u--;
-			r = 0;
-		}
-	}
-	
-	return tmp;
-}
-
-// ============================================================================
-std::vector<str> str::split (str del, bool bNoBlanks) {
-	std::vector<str> res;
-	unsigned int a = 0;
-	
-	// Find all separators and store the text left to them.
-	while (1) {
-		int b = first (del, a);
-		
-		if (b == -1)
-			break;
-		
-		if (!bNoBlanks || (b - a))
-			res.push_back (substr (a, b));
-		
-		a = b + strlen (del);
-	}
-	
-	// Add the string at the right of the last separator
-	if (!bNoBlanks || (len () - a))
-		res.push_back (substr (a, len ()));
-	return res;
-}
-
-std::vector<str> str::operator/ (str splitstring) {return split(splitstring);}
-std::vector<str> str::operator/ (char* splitstring) {return split(splitstring);}
-std::vector<str> str::operator/ (const char* splitstring) {return split(splitstring);}
-
-str& str::operator+= (vertex vrt) {
-	appendformat ("%s", vrt.stringRep (false).chars());
-	return *this;
-}
-
-str fmt (const char* fmt, ...) {
-	va_list va;
-	char* buf;
-	
-	va_start (va, fmt);
-	buf = vdynformat (fmt, va, 256);
-	va_end (va);
-	
-	str val = buf;
-	delete[] buf;
-	return val;
-}
\ No newline at end of file
--- a/str.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,271 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef STR_H
-#define STR_H
-
-#include <string.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <vector>
-#include <QString>
-
-char* vdynformat (const char* csFormat, va_list vArgs, long int lSize);
-
-class vertex;
-
-// Dynamic string object, allocates memory when needed and
-// features a good bunch of manipulation methods
-class str {
-private:
-	// The actual message
-	char* text;
-	
-	// Where will append() place new characters?
-	unsigned short curs;
-	
-	// Allocated length
-	unsigned short alloclen;
-	
-	// Resize the text buffer to len characters
-	void resize (unsigned int len);
-	
-public:
-	// ======================================================================
-	str ();
-	str (const char* c);
-	str (char c);
-	str (QString c);
-	~str ();
-	
-	// ======================================================================
-	// METHODS
-	
-	// Empty the string
-	void clear ();
-	
-	// Length of the string
-	size_t len () {
-		return strlen (text);
-	}
-	
-	// The char* form of the string
-	char* chars ();
-	
-	// Dumps the character table of the string
-	void dump ();
-	
-	// Appends text to the string
-	void append (const char c);
-	void append (const char* c);
-	void append (str c);
-	void append (QString c);
-	
-	// Formats text to the string.
-	void format (const char* fmt, ...);
-	
-	// Appends formatted text to the string.
-	void appendformat (const char* c, ...);
-	
-	// Returns the first occurrence of c in the string, optionally starting
-	// from a certain position rather than the start.
-	int first (const char* c, unsigned int a = 0);
-	
-	// Returns the last occurrence of c in the string, optionally starting
-	// from a certain position rather than the end.
-	int last (const char* c, int a = -1);
-	
-	// Returns a substring of the string, from a to b.
-	str substr (unsigned int a, unsigned int b);
-	
-	// Replace a substring with another substring.
-	void replace (const char* o, const char* n, unsigned int a = 0);
-	
-	// Removes a given index from the string, optionally more characters than just 1.
-	void remove (unsigned int idx, unsigned int dellen=1);
-	
-	// Trims the given amount of characters. If negative, the characters
-	// are removed from the beginning of the string, if positive, from the
-	// end of the string.
-	str trim (int dellen);
-	
-	// Inserts a substring into a certain position.
-	void insert (char* c, unsigned int pos);
-	
-	// Reverses the string.
-	str reverse ();
-	
-	// Repeats the string a given amount of times.
-	str repeat (int n);
-	
-	// Is the string a number?
-	bool isnumber ();
-	
-	// Is the string a word, i.e consists only of alphabetic letters?
-	bool isword ();
-	
-	// Convert string to lower case
-	str tolower ();
-	
-	// Convert string to upper case
-	str toupper ();
-	
-	// Compare this string with another
-	int compare (const char* c);
-	int compare (str c);
-	int icompare (str c);
-	int icompare (const char* c);
-	
-	// Counts the amount of substrings in the string
-	unsigned int count (char* s);
-	unsigned int count (char s);
-	
-	// Counts where the given substring is seen for the nth time
-	int instanceof (const char* s, unsigned n);
-	
-	char subscript (uint pos) {
-		return operator[] (pos);
-	}
-	
-	std::vector<str> split (str del, bool bNoBlanks = false);
-	
-	str strip (char c);
-	str strip (std::initializer_list<char> unwanted);
-	
-	// ======================================================================
-	// OPERATORS
-	str operator+ (str& c) {
-		append (c);
-		return *this;
-	}
-	
-	str& operator+= (char c) {
-		append (c);
-		return *this;
-	}
-	
-	str& operator+= (const char* c) {
-		append (c);
-		return *this;
-	}
-	
-	str& operator+= (const str c) {
-		append (c);
-		return *this;
-	}
-	
-	str& operator+= (const QString c) {
-		append (c);
-		return *this;
-	}
-	
-	str& operator+= (vertex vrt);
-	
-	str operator* (const int repcount) {
-		repeat (repcount);
-		return *this;
-	}
-	
-	str& operator*= (const int repcount) {
-		str other = repeat (repcount);
-		clear ();
-		append (other);
-		return *this;
-	}
-	
-	str operator- (const int trimcount) {
-		return trim (trimcount);
-	}
-	
-	str& operator-= (const int trimcount) {
-		str other = trim (trimcount);
-		clear ();
-		append (other);
-		return *this;
-	}
-	
-	std::vector<str> operator/ (str splitstring);
-	std::vector<str> operator/ (char* splitstring);
-	std::vector<str> operator/ (const char* splitstring);
-	
-	int operator% (str splitstring) {
-		return count (splitstring.chars());
-	}
-	
-	int operator% (char* splitstring) {
-		return count (splitstring);
-	}
-	
-	int operator% (const char* splitstring) {
-		return count (str (splitstring).chars());
-	}
-	
-	str operator+ () {
-		return toupper ();
-	}
-	
-	str operator- () {
-		return tolower ();
-	}
-	
-	str operator! () {
-		return reverse ();
-	}
-	
-	size_t operator~ () {
-		return len ();
-	}
-	
-#define DEFINE_OPERATOR_TYPE(OPER, TYPE) \
-	bool operator OPER (TYPE other) {return compare(other) OPER 0;}
-#define DEFINE_OPERATOR(OPER) \
-	DEFINE_OPERATOR_TYPE (OPER, str) \
-	DEFINE_OPERATOR_TYPE (OPER, char*) \
-	DEFINE_OPERATOR_TYPE (OPER, const char*)
-	
-	DEFINE_OPERATOR (==)
-	DEFINE_OPERATOR (!=)
-	DEFINE_OPERATOR (>)
-	DEFINE_OPERATOR (<)
-	DEFINE_OPERATOR (>=)
-	DEFINE_OPERATOR (<=)
-	
-	char& operator[] (int pos) {
-		return text[pos];
-	}
-	
-	operator char* () const {
-		return text;
-	}
-	
-	operator QString () const {
-		return text;
-	}
-	
-	operator int () const {
-		return atoi (text);
-	}
-	
-	operator uint () const {
-		return operator int();
-	}
-};
-
-str fmt (const char* fmt, ...);
-
-#endif // STR_H
--- a/types.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-#include <assert.h>
-#include "common.h"
-#include "types.h"
-#include "misc.h"
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-vertex vertex::midpoint (vertex& other) {
-	vertex mid;
-	
-	for (const Axis ax : g_Axes)
-		mid[ax] = (m_coords[ax] + other[ax]) / 2;
-	
-	return mid;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-str vertex::stringRep (const bool mangled) {
-	return fmt (mangled ? "(%s, %s, %s)" : "%s %s %s",
-		ftoa (coord (X)).chars(),
-		ftoa (coord (Y)).chars(),
-		ftoa (coord (Z)).chars());
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void vertex::transform (matrix<3> matr, vertex pos) {
-	double x2 = (matr[0] * x ()) + (matr[1] * y ()) + (matr[2] * z ()) + pos[X];
-	double y2 = (matr[3] * x ()) + (matr[4] * y ()) + (matr[5] * z ()) + pos[Y];
-	double z2 = (matr[6] * x ()) + (matr[7] * y ()) + (matr[8] * z ()) + pos[Z];
-	
-	x () = x2;
-	y () = y2;
-	z () = z2;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void vertex::transform (matrix<4> matr) {
-	double x2 = (matr[0] * x ()) + (matr[4] + y ()) + (matr[8] + z ()) + matr[12];
-	double y2 = (matr[1] * x ()) + (matr[5] + y ()) + (matr[9] + z ()) + matr[13];
-	double z2 = (matr[2] * x ()) + (matr[6] + y ()) + (matr[10] + z ()) + matr[14];
-	double w2 = (matr[3] * x ()) + (matr[7] + y ()) + (matr[11] + z ()) + matr[15];
-	
-	x () = x2 / w2;
-	y () = y2 / w2;
-	z () = z2 / w2;
-}
\ No newline at end of file
--- a/types.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef TYPES_H
-#define TYPES_H
-
-#include "common.h"
-#include "misc.h"
-
-typedef unsigned int uint;
-typedef short unsigned int ushort;
-typedef long unsigned int ulong;
-
-// Typedef out the _t suffices :)
-typedef int8_t int8;
-typedef int16_t int16;
-typedef int32_t int32;
-typedef int64_t int64;
-typedef uint8_t uint8;
-typedef uint16_t uint16;
-typedef uint32_t uint32;
-typedef uint64_t uint64;
-
-template<class T> using initlist = std::initializer_list<T>;
-using std::vector;
-
-enum Axis { X, Y, Z };
-static const Axis g_Axes[3] = {X, Y, Z};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-// matrix
-// 
-// A templated, mathematical N x N matrix
-// =============================================================================
-template<int N> class matrix {
-public:
-	// Constructors
-	matrix () {}
-	matrix (initlist<double> vals) {
-		assert (vals.size() == N * N);
-		memcpy (&m_vals[0], &(*vals.begin ()), sizeof m_vals);
-	}
-	
-	matrix (double fillval) {
-		for (short i = 0; i < (N * N); ++i)
-			m_vals[i] = fillval;
-	}
-	
-	matrix (double vals[]) {
-		for (short i = 0; i < (N * N); ++i)
-			m_vals[i] = vals[i];
-	}
-	
-	template<int M> matrix (matrix<M> other) {
-		assert (M >= N);
-		
-		for (short i = 0; i < M; ++i)
-		for (short j = 0; j < M; ++j) {
-			const short idx = (i * M) + j;
-			m_vals[idx] = other[idx];
-		}
-	}
-	
-	matrix<N> mult (matrix<N> other) {
-		matrix val;
-		val.zero ();
-
-		for (short i = 0; i < N; ++i)
-		for (short j = 0; j < N; ++j)
-		for (short k = 0; k < N; ++k)
-			val[(i * N) + j] += m_vals[(i * N) + k] * other[(k * N) + j];
-
-		return val;
-	}
-	
-	matrix<N>& operator= (matrix<N> other) {
-		memcpy (&m_vals[0], &other.m_vals[0], sizeof (double) * N * N);
-		return *this;
-	}
-	
-	matrix<N> operator* (matrix<N> other) {
-		return mult (other);
-	}
-	
-	double& operator[] (const uint idx) {
-		return m_vals[idx];
-	}
-	
-	const double& operator[] (const uint idx) const {
-		return m_vals[idx];
-	}
-	
-	void zero () {
-		memset (&m_vals[0], 0, sizeof (double) * N * N);
-	}
-	
-	void puts () const {
-		for (short i = 0; i < N; ++i) {
-			for (short j = 0; j < N; ++j)
-				printf ("%*f\t", 10, m_vals[(i * N) + j]);
-			
-			printf ("\n");
-		}
-	}
-	
-	str stringRep () const {
-		str val;
-		for (short i = 0; i < N * N; ++i) {
-			if (i > 0)
-				val += ' ';
-			
-			val.appendformat ("%s", ftoa (m_vals[i]).chars());
-		}
-		
-		return val;	
-	}
-
-private:
-	double m_vals[N * N];
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-// vertex
-// 
-// Vertex class, contains a single point in 3D space. Not to be confused with
-// LDVertex, which is a vertex used in an LDraw part file.
-// =============================================================================
-class vertex {
-public:
-	double m_coords[3];
-	
-	vertex () {}
-	vertex (double x, double y, double z) {
-		m_coords[X] = x;
-		m_coords[Y] = y;
-		m_coords[Z] = z;
-	}
-	
-	void move (vertex other) {
-		for (const Axis ax : g_Axes)
-			m_coords[ax] += other[ax];
-	}
-	
-	vertex& operator+= (vertex other) {
-		move (other);
-		return *this;
-	}
-	
-	vertex operator/ (const double d) const {
-		vertex other (*this);
-		return other /= d;
-	}
-	
-	vertex& operator/= (const double d) {
-		for (const Axis ax : g_Axes)
-			m_coords[ax] /= d;
-		return *this;
-	}
-	
-	bool operator== (const vertex& other) const {
-		return coord (X) == other[X] &&
-			coord (Y) == other[Y] &&
-			coord (Z) == other[Z];
-	}
-	
-	bool operator!= (const vertex& other) const {
-		return !operator== (other);
-	}
-	
-	vertex operator- () const {
-		return vertex (-m_coords[X], -m_coords[Y], -m_coords[Z]);
-	}
-	
-	double& coord (const ushort n) {
-		return m_coords[n];
-	}
-	
-	const double& coord (const ushort n) const {
-		return m_coords[n];
-	}
-	
-	double& operator[] (const Axis ax) {
-		return coord ((ushort) ax);
-	}
-	
-	const double& operator[] (const Axis ax) const {
-		return coord ((ushort) ax);
-	}
-	
-	double& x () { return m_coords[X]; }
-	double& y () { return m_coords[Y]; }
-	double& z () { return m_coords[Z]; }
-	const double& x () const { return m_coords[X]; }
-	const double& y () const { return m_coords[Y]; }
-	const double& z () const { return m_coords[Z]; }
-	
-	vertex midpoint (vertex& other);
-	str stringRep (const bool mangled);
-	void transform (matrix<3> matr, vertex pos);
-	void transform (matrix<4> matr);
-};
-
-#endif // TYPES_H
\ No newline at end of file
--- a/zz_aboutDialog.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdlib.h>
-#include <qlabel.h>
-#include <qboxlayout.h>
-#include <qdialogbuttonbox.h>
-#include <qdesktopservices.h>
-#include <qurl.h>
-#include "common.h"
-#include "zz_aboutDialog.h"
-
-AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) {
-	QWidget* mainTab, *licenseTab;
-	QTabWidget* tabs = new QTabWidget;
-	
-	{
-		mainTab = new QWidget;
-		
-		// Application icon - in full 64 x 64 glory.
-		QLabel* icon = new QLabel;
-		icon->setPixmap (getIcon ("ldforge"));
-		
-		// Heading - application label and copyright information
-		QLabel* title = new QLabel (fmt ("<b>" APPNAME " v%d.%d</b><br />"
-			"Copyright (C) 2013 Santeri Piippo",
-			VERSION_MAJOR, VERSION_MINOR));
-		
-		// Body text
-		QLabel* info = new QLabel (
-			"<p>This software is intended for usage as a parts<br />"
-			"authoring tool for the <a href=\"http://ldraw.org/\">LDraw</a> parts library.</p>"
-			
-			"<p>" APPNAME " is free software, and you are welcome<br />"
-			"to redistribute it under the terms of GPL v3. See the LICENSE<br />"
-			"text file or the license tab in this dialog for details. If the<br />"
-			"license text is not available for some reason, see<br />"
-			"<a href=\"http://www.gnu.org/licenses/\">http://www.gnu.org/licenses/</a>"
-			"for the license terms.</p>"
-			
-			"<p>The application icon is derived from "
-			"<a href=\"http://en.wikipedia.org/wiki/File:Anvil,_labelled_en.svg\">this image</a>.</p>"
-		);
-		
-		// Rest in peace, James.
-		QLabel* memorial = new QLabel ("In living memory of James Jessiman.");
-		
-		QVBoxLayout* layout = new QVBoxLayout;
-		layout->addWidget (icon);
-		layout->addWidget (title);
-		layout->addWidget (info);
-		layout->addWidget (memorial);
-		
-		// Align everything to the center.
-		for (QLabel* label : vector<QLabel*> ({icon, title, info, memorial}))
-			label->setAlignment (Qt::AlignCenter);
-		
-		mainTab->setLayout (layout);
-		tabs->addTab (mainTab, "About " APPNAME);
-	}
-	
-	{
-		licenseTab = new QWidget;
-		
-		QTextEdit* license = new QTextEdit;
-		license->setReadOnly (true);
-		
-		QFont font ("Monospace");
-		font.setStyleHint (QFont::TypeWriter);
-		font.setPixelSize (10);
-		
-		license->setFont (font);
-		
-		// Make the text view wide enough to display the license text.
-		// Why isn't 80 sufficient here?
-		license->setMinimumWidth (license->fontMetrics ().width ('a') * 85);
-		
-		// Try open the license text
-		FILE* fp = fopen ("LICENSE", "r");
-		
-		if (fp == null) {
-			// Failed; tell the user how to get the license text instead.
-			setlocale (LC_ALL, "C");
-			char const* text = "Couldn't open LICENSE: %s.<br />"
-				"See <a href=\"http://www.gnu.org/licenses/\">http://www.gnu.org/licenses/</a> for the GPLv3 text.";
-			
-			license->setHtml (fmt (text, strerror (errno)));
-		} else {
-			// Figure out file size
-			fseek (fp, 0, SEEK_END);
-			const size_t length = ftell (fp);
-			rewind (fp);
-			
-			// Init text buffer and write pointer
-			char* licenseText = new char[length];
-			char* writePtr = &licenseText[0];
-			
-			// Read in the license text
-			while (true) {
-				*writePtr = fgetc (fp);
-				
-				if (feof (fp))
-					break;
-				
-				writePtr++;
-			}
-			
-			// Add terminating null character and add the license text to the
-			// license dialog text view.
-			*writePtr = '\0';
-			license->setText (licenseText);
-			
-			// And dump the trash on the way out.
-			delete[] licenseText;
-		}
-		
-		QVBoxLayout* layout = new QVBoxLayout;
-		layout->addWidget (license);
-		licenseTab->setLayout (layout);
-		tabs->addTab (licenseTab, "License");
-	}
-	
-	QDialogButtonBox* buttons = new QDialogButtonBox (QDialogButtonBox::Close);
-	QPushButton* helpButton = new QPushButton;
-	
-	helpButton->setText ("Mail Author");
-	helpButton->setIcon (getIcon ("mail"));
-	buttons->addButton (static_cast<QAbstractButton*> (helpButton), QDialogButtonBox::HelpRole);
-	connect (buttons, SIGNAL (helpRequested ()), this, SLOT (slot_mail ()));
-	connect (buttons, SIGNAL (rejected ()), this, SLOT (reject ()));
-	
-	QVBoxLayout* layout = new QVBoxLayout;
-	layout->addWidget (tabs);
-	layout->addWidget (buttons);
-	setLayout (layout);
-	setWindowTitle ("About " APPNAME);
-}
-
-void AboutDialog::slot_mail () {
-	QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <arezey@gmail.com>?subject=LDForge"));
-}
\ No newline at end of file
--- a/zz_aboutDialog.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef ZZ_ABOUTDIALOG_H
-#define ZZ_ABOUTDIALOG_H
-
-#include <qdialog.h>
-#include "gui.h"
-
-class AboutDialog : public QDialog {
-	Q_OBJECT
-	
-public:
-	AboutDialog (QWidget* parent = nullptr, Qt::WindowFlags f = 0);
-	QPushButton* pb_mailAuthor;
-	
-private slots:
-	void slot_mail ();
-};
-
-#endif // ZZ_ABOUTDIALOG_H
\ No newline at end of file
--- a/zz_addObjectDialog.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,511 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <qgridlayout.h>
-#include <qradiobutton.h>
-#include <qcheckbox.h>
-#include "gui.h"
-#include "zz_addObjectDialog.h"
-#include "file.h"
-#include "colors.h"
-#include "zz_colorSelectDialog.h"
-#include "history.h"
-#include "zz_setContentsDialog.h"
-
-#define APPLY_COORDS(OBJ, N) \
-	for (short i = 0; i < N; ++i) \
-		for (const Axis ax : g_Axes) \
-			OBJ->vaCoords[i][ax] = dlg.dsb_coords[(i * 3) + ax]->value ();
-
-// =============================================================================
-class SubfileListItem : public QTreeWidgetItem {
-public:
-	SubfileListItem (QTreeWidgetItem* parent, int subfileID) :
-		QTreeWidgetItem (parent), subfileID (subfileID) {}
-	SubfileListItem (QTreeWidget* parent, int subfileID) :
-		QTreeWidgetItem (parent), subfileID (subfileID) {}
-	
-	int subfileID;
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) :
-	QDialog (parent)
-{
-	setlocale (LC_ALL, "C");
-	
-	short coordCount = 0;
-	
-	switch (type) {
-	case LDObject::Comment:
-		le_comment = new QLineEdit;
-		if (obj)
-			le_comment->setText (static_cast<LDComment*> (obj)->text);
-		break;
-	
-	case LDObject::Line:
-		coordCount = 6;
-		break;
-	
-	case LDObject::Triangle:
-		coordCount = 9;
-		break;
-	
-	case LDObject::Quad:
-	case LDObject::CondLine:
-		coordCount = 12;
-		break;
-	
-	case LDObject::Vertex:
-		coordCount = 3;
-		break;
-	
-	case LDObject::BFC:
-		rb_bfcType = new RadioBox ("Statement", {}, 0, Qt::Vertical);
-		
-		for (int i = 0; i < LDBFC::NumStatements; ++i)
-			rb_bfcType->addButton (new QRadioButton (LDBFC::statements[i]));
-		
-		if (obj)
-			rb_bfcType->setValue ((int) static_cast<LDBFC*> (obj)->type);
-		break;
-	
-	case LDObject::Subfile:
-		coordCount = 3;
-		
-		enum {
-			Parts,
-			Subparts,
-			Primitives,
-			HiRes,
-		};
-		
-		tw_subfileList = new QTreeWidget ();
-		for (int i : vector<int> ({Parts, Subparts, Primitives, HiRes})) {
-			SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, -1);
-			parentItem->setText (0, (i == Parts) ? "Parts" :
-				(i == Subparts) ? "Subparts" :
-				(i == Primitives) ? "Primitives" :
-				"Hi-Res");
-			
-			ulong j = 0;
-			for (partListEntry& part : g_PartList) {
-				QList<QTreeWidgetItem*> subfileItems;
-				
-				str fileName = part.sName;
-				const bool isSubpart = fileName.substr (0, 2) == "s\\";
-				const bool isPrimitive = str (part.sTitle).substr (0, 9) == "Primitive";
-				const bool isHiRes = fileName.substr (0, 3) == "48\\";
-				
-				if ((i == Subparts && isSubpart) ||
-					(i == Primitives && isPrimitive) ||
-					(i == HiRes && isHiRes) ||
-					(i == Parts && !isSubpart && !isPrimitive && !isHiRes))
-				{
-					SubfileListItem* item = new SubfileListItem (parentItem, j);
-					item->setText (0, fmt ("%s - %s", part.sName, part.sTitle));
-					subfileItems.append (item);
-				}
-				
-				j++;
-			}
-			
-			tw_subfileList->addTopLevelItem (parentItem);
-		}
-		
-		connect (tw_subfileList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_subfileTypeChanged ()));
-		lb_subfileName = new QLabel ("File:");
-		le_subfileName = new QLineEdit;
-		le_subfileName->setFocus ();
-		
-		if (obj) {
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			le_subfileName->setText (ref->zFileName);
-		}
-		break;
-	
-	case LDObject::Radial:
-		coordCount = 3;
-		
-		lb_radType = new QLabel ("Type:");
-		lb_radResolution = new QLabel ("Resolution:");
-		lb_radSegments = new QLabel ("Segments:");
-		lb_radRingNum = new QLabel ("Ring number:");
-		
-		rb_radType = new RadioBox ("Type", {}, 0, Qt::Vertical);
-		
-		for (int i = 0; i < LDRadial::NumTypes; ++i) {
-			if (i % (LDRadial::NumTypes / 2) == 0)
-				rb_radType->rowBreak ();
-			
-			rb_radType->addButton (new QRadioButton (LDRadial::radialTypeName ((LDRadial::Type) i)));
-		}
-		
-		connect (rb_radType, SIGNAL (sig_buttonPressed (int)), this, SLOT (slot_radialTypeChanged (int)));
-		
-		cb_radHiRes = new QCheckBox ("Hi-Res");
-		
-		sb_radSegments = new QSpinBox;
-		sb_radSegments->setMinimum (1);
-		
-		sb_radRingNum = new QSpinBox;
-		sb_radRingNum->setEnabled (false);
-		
-		if (obj) {
-			LDRadial* rad = static_cast<LDRadial*> (obj);
-			
-			rb_radType->setValue (rad->eRadialType);
-			sb_radSegments->setValue (rad->dSegments);
-			cb_radHiRes->setChecked ((rad->dDivisions == 48) ? Qt::Checked : Qt::Unchecked);
-			sb_radRingNum->setValue (rad->dRingNum);
-		}
-		break;
-	
-	default:
-		assert (false);
-		return;
-	}
-	
-	QPixmap icon = getIcon (fmt ("add-%s", g_saObjTypeIcons[type]));
-	LDObject* defaults = LDObject::getDefault (type);
-	
-	lb_typeIcon = new QLabel;
-	lb_typeIcon->setPixmap (icon);
-	
-	// Show a color edit dialog for the types that actually use the color
-	if (defaults->isColored ()) {
-		if (obj != null)
-			dColor = obj->dColor;
-		else
-			dColor = (type == LDObject::CondLine || type == LDObject::Line) ? edgecolor : maincolor;
-		
-		pb_color = new QPushButton;
-		setButtonBackground (pb_color, dColor);
-		connect (pb_color, SIGNAL (clicked ()), this, SLOT (slot_colorButtonClicked ()));
-	}
-	
-	for (short 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);
-	}
-	
-	IMPLEMENT_DIALOG_BUTTONS
-	
-	QGridLayout* const layout = new QGridLayout;
-	layout->addWidget (lb_typeIcon, 0, 0);
-	
-	switch (type) {
-	case LDObject::Line:
-	case LDObject::CondLine:
-	case LDObject::Triangle:
-	case LDObject::Quad:
-		// Apply coordinates
-		if (obj) {
-			for (short i = 0; i < coordCount / 3; ++i)
-			for (short j = 0; j < 3; ++j)
-				dsb_coords[(i * 3) + j]->setValue (obj->vaCoords[i].coord (j));
-		}
-		break;
-	
-	case LDObject::Comment:
-		layout->addWidget (le_comment, 0, 1);
-		break;
-	
-	case LDObject::BFC:
-		layout->addWidget (rb_bfcType, 0, 1);
-		break;
-	
-	case LDObject::Radial:
-		layout->addWidget (rb_radType, 1, 1, 3, 2);
-		layout->addWidget (cb_radHiRes, 1, 3);
-		layout->addWidget (lb_radSegments, 2, 3);
-		layout->addWidget (sb_radSegments, 2, 4);
-		layout->addWidget (lb_radRingNum, 3, 3);
-		layout->addWidget (sb_radRingNum, 3, 4);
-		
-		if (obj)
-			for (short i = 0; i < 3; ++i)
-				dsb_coords[i]->setValue (static_cast<LDRadial*> (obj)->vPosition.coord (i));
-		break;
-	
-	case LDObject::Subfile:
-		layout->addWidget (tw_subfileList, 1, 1, 1, 2);
-		layout->addWidget (lb_subfileName, 2, 1);
-		layout->addWidget (le_subfileName, 2, 2);
-		
-		if (obj)
-			for (short i = 0; i < 3; ++i)
-				dsb_coords[i]->setValue (static_cast<LDSubfile*> (obj)->vPosition.coord (i));
-		break;
-	
-	default:
-		break;
-	}
-	
-	if (type == LDObject::Subfile || type == LDObject::Radial) {
-		QLabel* lb_matrix = new QLabel ("Matrix:");
-		le_matrix = new QLineEdit;
-		// le_matrix->setValidator (new QDoubleValidator);
-		matrix<3> defval = g_identity;
-		
-		if (obj) {
-			if (obj->getType () == LDObject::Subfile)
-				defval = static_cast<LDSubfile*> (obj)->mMatrix;
-			else
-				defval = static_cast<LDRadial*> (obj)->mMatrix;
-		}
-		
-		le_matrix->setText (defval.stringRep ());
-		layout->addWidget (lb_matrix, 4, 1);
-		layout->addWidget (le_matrix, 4, 2, 1, 3);
-	}
-	
-	if (defaults->isColored ())
-		layout->addWidget (pb_color, 1, 0);
-	
-	if (coordCount > 0) {
-		QGridLayout* const qCoordLayout = new QGridLayout;
-		
-		for (short i = 0; i < coordCount; ++i)
-			qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3));
-		
-		layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3);
-	}
-	
-	layout->addWidget (bbx_buttons, 5, 0, 1, 4);
-	setLayout (layout);
-	setWindowTitle (fmt (APPNAME ": New %s",
-		g_saObjTypeNames[type]).chars());
-	
-	setWindowIcon (icon);
-	delete defaults;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void AddObjectDialog::setButtonBackground (QPushButton* button, short color) {
-	button->setIcon (getIcon ("palette"));
-	button->setAutoFillBackground (true);
-	button->setStyleSheet (
-		fmt ("background-color: %s", getColor (color)->zColorString.chars()).chars()
-	);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-char* AddObjectDialog::currentSubfileName() {
-	SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem ());
-	
-	if (item->subfileID == -1)
-		return null; // selected a heading
-	
-	return g_PartList[item->subfileID].sName;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void AddObjectDialog::slot_colorButtonClicked () {
-	ColorSelectDialog::staticDialog (dColor, dColor, this);
-	setButtonBackground (pb_color, dColor);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void AddObjectDialog::slot_radialTypeChanged (int dType) {
-	LDRadial::Type eType = (LDRadial::Type) dType;
-	sb_radRingNum->setEnabled (eType == LDRadial::Ring || eType == LDRadial::Cone);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void AddObjectDialog::slot_subfileTypeChanged () {
-	char* name = currentSubfileName ();
-	
-	if (name)
-		le_subfileName->setText (name);
-}
-
-// =============================================================================
-template<class T> T* initObj (LDObject*& obj) {
-	if (obj == null)
-		obj = new T;
-	
-	return static_cast<T*> (obj);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj) {
-	setlocale (LC_ALL, "C");
-	
-	// Redirect editing of gibberish to the set contents dialog
-	if (obj && obj->getType () == LDObject::Gibberish) {
-		SetContentsDialog::staticDialog (obj);
-		return;
-	}
-	
-	if (type == LDObject::Empty)
-		return; // Nothing to edit with empties
-	
-	const bool newObject = (obj == null);
-	AddObjectDialog dlg (type, obj);
-	
-	if (obj)
-		assert (obj->getType () == type);
-	
-	if (dlg.exec () == false)
-		return;
-	
-	LDObject* backup = null;
-	if (!newObject)
-		backup = obj->clone ();
-	
-	matrix<3> transform = g_identity;
-	if (type == LDObject::Subfile || type == LDObject::Radial) {
-		vector<str> matrixstrvals = str (dlg.le_matrix->text ()).split (" ", true);
-		
-		if (matrixstrvals.size () == 9) {
-			double matrixvals[9];
-			int i = 0;
-			
-			for (str val : matrixstrvals)
-				matrixvals[i++] = atof (val);
-			
-			transform = matrix<3> (matrixvals);
-		}
-	}
-	
-	switch (type) {
-	case LDObject::Comment:
-		{
-			LDComment* comm = initObj<LDComment> (obj);
-			comm->text = dlg.le_comment->text ();
-		}
-		break;
-	
-	case LDObject::Line:
-		{
-			LDLine* line = initObj<LDLine> (obj);
-			line->dColor = dlg.dColor;
-			APPLY_COORDS (line, 2)
-		}
-		break;
-	
-	case LDObject::Triangle:
-		{
-			LDTriangle* tri = initObj<LDTriangle> (obj);
-			tri->dColor = dlg.dColor;
-			APPLY_COORDS (tri, 3)
-		}
-		break;
-	
-	case LDObject::Quad:
-		{
-			LDQuad* quad = initObj<LDQuad> (obj);
-			quad->dColor = dlg.dColor;
-			APPLY_COORDS (quad, 4)
-		}
-		break;
-	
-	case LDObject::CondLine:
-		{
-			LDCondLine* line = initObj<LDCondLine> (obj);
-			line->dColor = dlg.dColor;
-			APPLY_COORDS (line, 4)
-		}
-		break;
-	
-	case LDObject::BFC:
-		{
-			LDBFC* bfc = initObj<LDBFC> (obj);
-			bfc->type = (LDBFC::Type) dlg.rb_bfcType->value ();
-		}
-		break;
-	
-	case LDObject::Vertex:
-		{
-			LDVertex* vert = initObj<LDVertex> (obj);
-			vert->dColor = dlg.dColor;
-			
-			for (const Axis ax : g_Axes)
-				vert->vPosition[ax] = dlg.dsb_coords[ax]->value ();
-		}
-		break;
-	
-	case LDObject::Radial:
-		{
-			LDRadial* pRad = initObj<LDRadial> (obj);
-			pRad->dColor = dlg.dColor;
-			
-			for (const Axis ax : g_Axes)
-				pRad->vPosition[ax] = dlg.dsb_coords[ax]->value ();
-			
-			pRad->dDivisions = (dlg.cb_radHiRes->checkState () != Qt::Checked) ? 16 : 48;
-			pRad->dSegments = min<short> (dlg.sb_radSegments->value (), pRad->dDivisions);
-			pRad->eRadialType = (LDRadial::Type) dlg.rb_radType->value ();
-			pRad->dRingNum = dlg.sb_radRingNum->value ();
-			pRad->mMatrix = transform;
-		}
-		break;
-	
-	case LDObject::Subfile:
-		{
-			str name = dlg.le_subfileName->text ();
-			if (~name == 0)
-				return; // no subfile filename
-			
-			OpenFile* file = loadSubfile (name);
-			if (!file)
-				return;
-			
-			LDSubfile* ref = initObj<LDSubfile> (obj);
-			ref->dColor = dlg.dColor;
-			
-			for (const Axis ax : g_Axes)
-				ref->vPosition[ax] = dlg.dsb_coords[ax]->value ();
-			
-			ref->zFileName = name;
-			ref->mMatrix = transform;
-			ref->pFile = file;
-		}
-		break;
-	
-	default:
-		break;
-	}
-	
-	if (newObject) {
-		ulong idx = g_win->getInsertionPoint ();
-		g_curfile->insertObj (idx, obj);
-		History::addEntry (new AddHistory ({(ulong) idx}, {obj->clone ()}));
-	} else {
-		History::addEntry (new EditHistory ({(ulong) obj->getIndex (g_curfile)}, {backup}, {obj->clone ()}));
-	}
-	
-	g_win->refresh ();
-}
\ No newline at end of file
--- a/zz_addObjectDialog.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef ZZ_ADDOBJECTDIALOG_H
-#define ZZ_ADDOBJECTDIALOG_H
-
-#include "gui.h"
-#include "radiobox.h"
-#include <qdialog.h>
-#include <qlineedit.h>
-#include <qdialogbuttonbox.h>
-#include <qcheckbox.h>
-#include <qspinbox.h>
-#include <qlabel.h>
-#include <qradiobutton.h>
-#include <qlistwidget.h>
-#include <qtreewidget.h>
-
-class AddObjectDialog : public QDialog {
-	Q_OBJECT
-	
-public:
-    AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent = null);
-	static void staticDialog (const LDObject::Type type, LDObject* obj);
-	
-	QLabel* lb_typeIcon;
-	
-	// Comment line edit
-	QLineEdit* le_comment;
-	
-	// Coordinate edits for.. anything with coordinates, really.
-	QDoubleSpinBox* dsb_coords[12];
-	
-	// Color selection dialog button
-	QPushButton* pb_color;
-	
-	// BFC-related widgets
-	RadioBox* rb_bfcType;
-	
-	// Subfile stuff
-	QTreeWidget* tw_subfileList;
-	QLineEdit* le_subfileName;
-	QLabel* lb_subfileName;
-	
-	// Radial stuff
-	QCheckBox* cb_radHiRes;
-	RadioBox* rb_radType;
-	QSpinBox* sb_radSegments, *sb_radRingNum;
-	QLabel* lb_radType, *lb_radResolution, *lb_radSegments,
-		*lb_radRingNum;
-	
-	QLineEdit* le_matrix;
-	
-	QDialogButtonBox* bbx_buttons;
-	
-private:
-	void setButtonBackground (QPushButton* button, short color);
-	char* currentSubfileName ();
-	
-	short dColor;
-	
-private slots:
-	void slot_colorButtonClicked ();
-	void slot_radialTypeChanged (int type);
-	void slot_subfileTypeChanged ();
-};
-
-#endif // ZZ_ADDOBJECTDIALOG_H
\ No newline at end of file
--- a/zz_colorSelectDialog.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,179 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "common.h"
-#include "gui.h"
-#include <qgraphicsscene.h>
-#include <qgraphicsview.h>
-#include <qicon.h>
-#include <qboxlayout.h>
-#include <qgraphicsitem.h>
-#include <qevent.h>
-#include <qscrollbar.h>
-#include "zz_colorSelectDialog.h"
-#include "colors.h"
-#include "config.h"
-#include "misc.h"
-
-static const short g_dNumColumns = 8;
-static const short g_dNumRows = 10;
-static const short g_dSquareSize = 32;
-static const long g_lWidth = (g_dNumColumns * g_dSquareSize);
-static const long g_lHeight = (g_dNumRows * g_dSquareSize);
-static const long g_lMaxHeight = ((MAX_COLORS / g_dNumColumns) * g_dSquareSize);
-
-extern_cfg (str, gl_maincolor);
-extern_cfg (float, gl_maincolor_alpha);
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-ColorSelectDialog::ColorSelectDialog (short int defval, QWidget* parent) : QDialog (parent) {
-	// Remove the default color if it's invalid
-	if (!getColor (defval))
-		defval = -1;
-	
-	gs_scene = new QGraphicsScene;
-	gv_view = new QGraphicsView (gs_scene);
-	selColor = defval;
-	
-	// not really an icon but eh
-	gs_scene->setBackgroundBrush (getIcon ("checkerboard"));
-	
-	gs_scene->setSceneRect (0, 0, g_lWidth, g_lMaxHeight);
-	gv_view->setSceneRect (0, 0, g_lWidth, g_lMaxHeight);
-	
-	drawScene ();
-	
-	IMPLEMENT_DIALOG_BUTTONS
-	
-	// Set the size of the view
-	const long lWidth = g_lWidth + 21; // HACK
-	gv_view->setMaximumWidth (lWidth);
-	gv_view->setMinimumWidth (lWidth);
-	gv_view->setMaximumHeight (g_lHeight);
-	gv_view->setMinimumHeight (g_lHeight);
-	gv_view->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
-	
-	// If we have a default color selected, scroll down so that it is visible.
-	// TODO: find a better way to do this
-	if (defval >= ((g_dNumColumns * g_dNumRows) - 2)) {
-		ulong ulNewY = ((defval / g_dNumColumns) - 3) * g_dSquareSize;
-		gv_view->verticalScrollBar ()->setSliderPosition (ulNewY);
-	}
-	
-	lb_colorInfo = new QLabel;
-	drawColorInfo ();
-	
-	QVBoxLayout* qLayout = new QVBoxLayout;
-	qLayout->addWidget (gv_view);
-	qLayout->addWidget (lb_colorInfo);
-	qLayout->addWidget (bbx_buttons);
-	setLayout (qLayout);
-	
-	setWindowIcon (getIcon ("palette"));
-	setWindowTitle (APPNAME);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ColorSelectDialog::drawScene () {
-	const double fPenWidth = 1.0f;
-	QPen qPen (Qt::black, fPenWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
-	
-	// Draw the color rectangles.
-	gs_scene->clear ();
-	for (short i = 0; i < MAX_COLORS; ++i) {
-		color* meta = getColor (i);
-		if (!meta)
-			continue;
-		
-		const double x = (i % g_dNumColumns) * g_dSquareSize;
-		const double y = (i / g_dNumColumns) * g_dSquareSize;
-		const double w = (g_dSquareSize) - (fPenWidth / 2);
-		
-		QColor col = meta->qColor;
-		
-		if (i == maincolor) {
-			// Use the user preferences for main color here
-			col = gl_maincolor.value.chars ();
-			col.setAlpha (gl_maincolor_alpha * 255.0f);
-		}
-		
-		bool dark = (luma (col) < 80);
-		
-		gs_scene->addRect (x, y, w, w, qPen, col);
-		QGraphicsTextItem* qText = gs_scene->addText (fmt ("%lu", i).chars());
-		qText->setDefaultTextColor ((dark) ? Qt::white : Qt::black);
-		qText->setPos (x, y);
-		
-		if (i == selColor) {
-			auto curspic = gs_scene->addPixmap (getIcon ("colorcursor"));
-			curspic->setPos (x, y);
-		}
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ColorSelectDialog::drawColorInfo () {
-	color* col = getColor (selColor);
-	
-	if (selColor == -1 || !col) {
-		lb_colorInfo->setText ("---");
-		return;
-	}
-	
-	lb_colorInfo->setText (fmt ("%d - %s",
-		selColor, col->zName.chars()));
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ColorSelectDialog::mousePressEvent (QMouseEvent* event) {
-	QPointF qPoint = gv_view->mapToScene (event->pos ());
-	
-	ulong x = ((ulong)qPoint.x () - (g_dSquareSize / 2)) / g_dSquareSize;
-	ulong y = ((ulong)qPoint.y () - (g_dSquareSize / 2)) / g_dSquareSize;
-	ulong idx = (y * g_dNumColumns) + x;
-	
-	color* col = getColor (idx);
-	if (!col)
-		return;
-	
-	selColor = idx;
-	drawScene ();
-	drawColorInfo ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-bool ColorSelectDialog::staticDialog (short& val, short int defval, QWidget* parent) {
-	ColorSelectDialog dlg (defval, parent);
-	
-	if (dlg.exec () && dlg.selColor != -1) {
-		val = dlg.selColor;
-		return true;
-	}
-	
-	return false;
-}
\ No newline at end of file
--- a/zz_colorSelectDialog.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef COLORSELECTOR_H
-#define COLORSELECTOR_H
-
-#include <qdialog.h>
-#include <qdialogbuttonbox.h>
-#include <qgraphicsscene.h>
-#include <qlabel.h>
-#include "common.h"
-
-class ColorSelectDialog : public QDialog {
-	Q_OBJECT
-	
-public:
-	explicit ColorSelectDialog (short defval = -1, QWidget* parent = null);
-	static bool staticDialog (short& val, short defval = -1, QWidget* parent = null);
-	
-	QGraphicsScene* gs_scene;
-	QGraphicsView* gv_view;
-	QLabel* lb_colorInfo;
-	QDialogButtonBox* bbx_buttons;
-	short selColor;
-	
-private:
-	void drawScene ();
-	void drawColorInfo ();
-	
-private slots:
-	void mousePressEvent (QMouseEvent* event);
-};
-
-#endif // COLORSELECTOR_H
\ No newline at end of file
--- a/zz_configDialog.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,796 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "common.h"
-#include "zz_configDialog.h"
-#include "file.h"
-#include "config.h"
-#include "misc.h"
-#include "colors.h"
-#include "zz_colorSelectDialog.h"
-#include <qgridlayout.h>
-#include <qfiledialog.h>
-#include <qcolordialog.h>
-#include <qboxlayout.h>
-#include <qevent.h>
-#include <qgroupbox.h>
-
-extern_cfg (str, gl_bgcolor);
-extern_cfg (str, gl_maincolor);
-extern_cfg (bool, lv_colorize);
-extern_cfg (bool, gl_colorbfc);
-extern_cfg (float, gl_maincolor_alpha);
-extern_cfg (int, gl_linethickness);
-extern_cfg (int, gui_toolbar_iconsize);
-extern_cfg (str, gui_colortoolbar);
-extern_cfg (bool, gl_selflash);
-extern_cfg (bool, edit_schemanticinline);
-extern_cfg (bool, gl_blackedges);
-
-ConfigDialog* g_ConfigDialog = null;
-
-#define INIT_CHECKBOX(BOX, CFG) \
-	BOX->setCheckState (CFG ? Qt::Checked : Qt::Unchecked);
-
-#define APPLY_CHECKBOX(BTN, CFG) \
-	CFG = BTN->checkState() == Qt::Checked;
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-ConfigDialog::ConfigDialog (ForgeWindow* parent) : QDialog (parent) {
-	g_ConfigDialog = this;
-	tabs = new QTabWidget;
-	
-	initMainTab ();
-	initShortcutsTab ();
-	initQuickColorTab ();
-	initGridTab ();
-	initExtProgTab ();
-	
-	IMPLEMENT_DIALOG_BUTTONS
-	
-	QVBoxLayout* layout = new QVBoxLayout;
-	layout->addWidget (tabs);
-	layout->addWidget (bbx_buttons);
-	setLayout (layout);
-	
-	setWindowTitle ("Settings");
-	setWindowIcon (getIcon ("settings"));
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::initMainTab () {
-	mainTab = new QWidget;
-	
-	// =========================================================================
-	// Background and foreground colors
-	lb_viewBg = new QLabel ("Background color:");
-	pb_viewBg = new QPushButton;
-	setButtonBackground (pb_viewBg, gl_bgcolor.value);
-	connect (pb_viewBg, SIGNAL (clicked ()),
-		this, SLOT (slot_setGLBackground ()));
-	pb_viewBg->setWhatsThis ("This is the background color for the viewport.");
-	
-	lb_viewFg = new QLabel ("Foreground color:");
-	pb_viewFg = new QPushButton;
-	setButtonBackground (pb_viewFg, gl_maincolor.value);
-	connect (pb_viewFg, SIGNAL (clicked ()),
-		this, SLOT (slot_setGLForeground ()));
-	pb_viewFg->setWhatsThis ("This color is used for the main color.");
-	
-	// =========================================================================
-	// Alpha and line thickness sliders
-	lb_viewFgAlpha = new QLabel ("Alpha:");
-	makeSlider (sl_viewFgAlpha, 1, 10, (gl_maincolor_alpha * 10.0f));
-	sl_viewFgAlpha->setWhatsThis ("Opacity of main color in the viewport.");
-	
-	lb_lineThickness = new QLabel ("Line thickness:");
-	makeSlider (sl_lineThickness, 1, 8, gl_linethickness);
-	sl_lineThickness->setWhatsThis ("How thick lines should be drawn in the viewport.");
-	
-	// =========================================================================
-	// Tool bar icon size slider
-	lb_iconSize = new QLabel ("Toolbar icon size:");
-	makeSlider (sl_iconSize, 1, 5, (gui_toolbar_iconsize - 12) / 4);
-	
-	// =========================================================================
-	// List view colorizer and BFC red/green view checkboxes
-	cb_colorize = new QCheckBox ("Colorize polygons in object list");
-	cb_colorize->setChecked (lv_colorize);
-	cb_colorize->setWhatsThis ("Makes colored objects (non-16 and 24) appear "
-		"colored in the object list. A red polygon will have its description "
-		"written in red text.");
-	
-	cb_colorBFC = new QCheckBox ("Red/green BFC view");
-	cb_colorBFC->setChecked (gl_colorbfc);
-	cb_colorBFC->setWhatsThis ("Polygons' front sides become green and back "
-		"sides red. Not implemented yet.");
-	
-	cb_selFlash = new QCheckBox ("Selection flash");
-	cb_selFlash->setChecked (gl_selflash);
-	cb_colorBFC->setWhatsThis ("A pulse effect for clearer selection view.");
-	
-	cb_blackEdges = new QCheckBox ("Black edges");
-	cb_blackEdges->setWhatsThis ("Makes all edgelines appear black. If this is "
-		"not set, edge lines take their color as defined in LDConfig.ldr");
-	cb_blackEdges->setChecked (gl_blackedges);
-	
-	cb_schemanticInline = new QCheckBox ("Schemantic insertion only");
-	cb_schemanticInline->setChecked (edit_schemanticinline);
-	cb_colorBFC->setWhatsThis ("When inserting objects through inlining, file "
-		"inserting or through external programs, all non-schemantics (those without "
-		"actual meaning in the part file like comments and such) are filtered out.");
-	
-	cb_schemanticInline->setEnabled (false);
-	cb_colorBFC->setEnabled (false);
-	
-	QGridLayout* layout = new QGridLayout;
-	layout->addWidget (lb_viewBg, 0, 0);
-	layout->addWidget (pb_viewBg, 0, 1);
-	layout->addWidget (lb_viewFg, 0, 2);
-	layout->addWidget (pb_viewFg, 0, 3);
-	
-	layout->addWidget (lb_lineThickness, 1, 0);
-	layout->addWidget (sl_lineThickness, 1, 1);
-	layout->addWidget (lb_viewFgAlpha, 1, 2);
-	layout->addWidget (sl_viewFgAlpha, 1, 3);
-	
-	layout->addWidget (lb_iconSize, 2, 0);
-	layout->addWidget (sl_iconSize, 2, 1);
-	
-	layout->addWidget (cb_colorize, 3, 0, 1, 4);
-	layout->addWidget (cb_colorBFC, 4, 0, 1, 4);
-	layout->addWidget (cb_selFlash, 5, 0, 1, 4);
-	layout->addWidget (cb_blackEdges, 6, 0, 1, 4);
-	layout->addWidget (cb_schemanticInline, 7, 0, 1, 4);
-	mainTab->setLayout (layout);
-	
-	// Add the tab to the manager
-	tabs->addTab (mainTab, "Main settings");
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::initShortcutsTab () {
-	shortcutsTab = new QWidget;
-	lw_shortcutList = new QListWidget;
-	lw_shortcutList->setAlternatingRowColors (true);
-	
-	shortcutsTab->setWhatsThis ("Here you can alter keyboard shortcuts for "
-		"almost all LDForge actions. Only exceptions are the controls for the "
-		"viewport. Use the set button to set a key shortcut, clear to remove it "
-		"and reset to restore the shortcut to its default value.\n"
-		"\tShortcut changes apply immediately after closing this dialog." );
-	
-	// Init table items
-	ulong i = 0;
-	for (actionmeta& info : g_ActionMeta) {
-		QAction* const act = *info.qAct;
-		
-		ShortcutListItem* item = new ShortcutListItem;
-		setShortcutText (item, info);
-		item->setIcon (act->icon ());
-		item->setActionInfo (&info);
-		
-		// 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"));
-		
-		lw_shortcutList->insertItem (i++, item);
-	}
-	
-	lw_shortcutList->setSortingEnabled (true);
-	lw_shortcutList->sortItems ();
-	
-	pb_setShortcut = new QPushButton ("Set");
-	pb_resetShortcut = new QPushButton ("Reset");
-	pb_clearShortcut = new QPushButton ("Clear");
-	
-	connect (pb_setShortcut, SIGNAL (clicked ()), this, SLOT (slot_setShortcut ()));
-	connect (pb_resetShortcut, SIGNAL (clicked ()), this, SLOT (slot_resetShortcut ()));
-	connect (pb_clearShortcut, SIGNAL (clicked ()), this, SLOT (slot_clearShortcut ()));
-	
-	QVBoxLayout* buttonLayout = new QVBoxLayout;
-	buttonLayout->addWidget (pb_setShortcut);
-	buttonLayout->addWidget (pb_resetShortcut);
-	buttonLayout->addWidget (pb_clearShortcut);
-	buttonLayout->addStretch (10);
-	
-	QGridLayout* layout = new QGridLayout;
-	layout->addWidget (lw_shortcutList, 0, 0);
-	layout->addLayout (buttonLayout, 0, 1);
-	shortcutsTab->setLayout (layout);
-	tabs->addTab (shortcutsTab, "Shortcuts");
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::initQuickColorTab () {
-	quickColorTab = new QWidget;
-	
-	pb_addColor = new QPushButton (getIcon ("palette"), "Add");
-	pb_delColor = new QPushButton (getIcon ("delete"), "Remove");
-	pb_changeColor = new QPushButton (getIcon ("palette"), "Set");
-	pb_addColorSeparator = new QPushButton ("Add Separator");
-	pb_moveColorUp = new QPushButton (getIcon ("arrow-up"), "Move Up");
-	pb_moveColorDown = new QPushButton (getIcon ("arrow-down"), "Move Down");
-	pb_clearColors = new QPushButton (getIcon ("delete-all"), "Clear");
-	lw_quickColors = new QListWidget;
-	
-	quickColorMeta = parseQuickColorMeta ();
-	updateQuickColorList ();
-	
-	QVBoxLayout* buttonLayout = new QVBoxLayout;
-	buttonLayout->addWidget (pb_addColor);
-	buttonLayout->addWidget (pb_delColor);
-	buttonLayout->addWidget (pb_changeColor);
-	buttonLayout->addWidget (pb_addColorSeparator);
-	buttonLayout->addWidget (pb_moveColorUp);
-	buttonLayout->addWidget (pb_moveColorDown);
-	buttonLayout->addWidget (pb_clearColors);
-	buttonLayout->addStretch (1);
-	
-	connect (pb_addColor, SIGNAL (clicked ()), this, SLOT (slot_setColor ()));
-	connect (pb_delColor, SIGNAL (clicked ()), this, SLOT (slot_delColor ()));
-	connect (pb_changeColor, SIGNAL (clicked ()), this, SLOT (slot_setColor ()));
-	connect (pb_addColorSeparator, SIGNAL (clicked ()), this, SLOT (slot_addColorSeparator ()));
-	connect (pb_moveColorUp, SIGNAL (clicked ()), this, SLOT (slot_moveColor ()));
-	connect (pb_moveColorDown, SIGNAL (clicked ()), this, SLOT (slot_moveColor ()));
-	connect (pb_clearColors, SIGNAL (clicked ()), this, SLOT (slot_clearColors ()));
-	
-	QGridLayout* layout = new QGridLayout;
-	layout->addWidget (lw_quickColors, 0, 0);
-	layout->addLayout (buttonLayout, 0, 1);
-	
-	quickColorTab->setLayout (layout);
-	tabs->addTab (quickColorTab, "Quick Colors");
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::initGridTab () {
-	QWidget* tab = new QWidget;
-	QGridLayout* layout = new QGridLayout;
-	QVBoxLayout* l2 = new QVBoxLayout;
-	
-	QLabel* xlabel = new QLabel ("X"),
-		*ylabel = new QLabel ("Y"),
-		*zlabel = new QLabel ("Z"),
-		*anglabel = new QLabel ("Angle");
-	
-	short i = 1;
-	for (QLabel* label : std::initializer_list<QLabel*> ({xlabel, ylabel, zlabel, anglabel})) {
-		label->setAlignment (Qt::AlignCenter);
-		layout->addWidget (label, 0, i++);
-	}
-	
-	for (int i = 0; i < g_NumGrids; ++i) {
-		// Icon
-		lb_gridIcons[i] = new QLabel;
-		lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%s", str (g_GridInfo[i].name).tolower ().chars ())));
-		
-		// Text label
-		lb_gridLabels[i] = new QLabel (fmt ("%s:", g_GridInfo[i].name));
-		
-		QHBoxLayout* labellayout = new QHBoxLayout;
-		labellayout->addWidget (lb_gridIcons[i]);
-		labellayout->addWidget (lb_gridLabels[i]);
-		layout->addLayout (labellayout, i + 1, 0);
-		
-		// Add the widgets
-		for (int j = 0; j < 4; ++j) {
-			dsb_gridData[i][j] = new QDoubleSpinBox;
-			dsb_gridData[i][j]->setValue (g_GridInfo[i].confs[j]->value);
-			layout->addWidget (dsb_gridData[i][j], i + 1, j + 1);
-		}
-	}
-	
-	l2->addLayout (layout);
-	l2->addStretch (1);
-	
-	tab->setLayout (l2);
-	tabs->addTab (tab, "Grids");
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-extern_cfg (str, prog_ytruder);
-extern_cfg (str, prog_rectifier);
-extern_cfg (str, prog_intersector);
-extern_cfg (str, prog_isecalc);
-static const struct extProgInfo {
-	const char* const name, *iconname;
-	strconfig* const path;
-	mutable QLineEdit* input;
-	mutable QPushButton* setPathButton;
-} g_extProgInfo[] = {
-	{ "Ytruder", "ytruder", &prog_ytruder, null, null },
-	{ "Rectifier", "rectifier", &prog_rectifier, null, null },
-	{ "Intersector", "intersector", &prog_intersector, null, null },
-	{ "Isecalc", "isecalc", &prog_isecalc, null, null },
-};
-
-void ConfigDialog::initExtProgTab () {
-	QWidget* tab = new QWidget;
-	QGridLayout* pathsLayout = new QGridLayout;
-	QGroupBox* pathsBox = new QGroupBox ("Paths", this);
-	QVBoxLayout* layout = new QVBoxLayout (this);
-	
-	ulong row = 0;
-	for (const extProgInfo& info : g_extProgInfo) {
-		QLabel* icon = new QLabel,
-			*progLabel = new QLabel (info.name);
-		QLineEdit* input = new QLineEdit;
-		QPushButton* setPathButton = new QPushButton ();
-		
-		icon->setPixmap (getIcon (info.iconname));
-		input->setText (info.path->value);
-		setPathButton->setIcon (getIcon ("folder"));
-		info.input = input;
-		info.setPathButton = setPathButton;
-		
-		connect (setPathButton, SIGNAL (clicked ()), this, SLOT (slot_setExtProgPath ()));
-		
-		pathsLayout->addWidget (icon, row, 0);
-		pathsLayout->addWidget (progLabel, row, 1);
-		pathsLayout->addWidget (input, row, 2);
-		pathsLayout->addWidget (setPathButton, row, 3);
-		++row;
-	}
-	
-	pathsBox->setLayout (pathsLayout);
-	layout->addWidget (pathsBox);
-	layout->addSpacing (10);
-	
-	tab->setLayout (layout);
-	tabs->addTab (tab, "Ext. Programs");
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::updateQuickColorList (quickColorMetaEntry* pSel) {
-	for (QListWidgetItem* qItem : quickColorItems)
-		delete qItem;
-	
-	quickColorItems.clear ();
-	
-	// Init table items
-	for (quickColorMetaEntry& entry : quickColorMeta) {
-		QListWidgetItem* qItem = new QListWidgetItem;
-		
-		if (entry.bSeparator) {
-			qItem->setText ("--------");
-			qItem->setIcon (getIcon ("empty"));
-		} else {
-			color* col = entry.col;
-			
-			if (col == null) {
-				qItem->setText ("[[unknown color]]");
-				qItem->setIcon (getIcon ("error"));
-			} else {
-				qItem->setText (col->zName);
-				qItem->setIcon (getIcon ("palette"));
-			}
-		}
-		
-		lw_quickColors->addItem (qItem);
-		quickColorItems.push_back (qItem);
-		
-		if (pSel && &entry == pSel) {
-			lw_quickColors->setCurrentItem (qItem);
-			lw_quickColors->scrollToItem (qItem);
-		}
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::slot_setColor () {
-	quickColorMetaEntry* entry = null;
-	QListWidgetItem* item = null;
-	const bool isNew = static_cast<QPushButton*> (sender ()) == pb_addColor;
-	
-	if (isNew == false) {
-		item = getSelectedQuickColor ();
-		if (!item)
-			return;
-		
-		ulong ulIdx = getItemRow (item, quickColorItems);
-		entry = &quickColorMeta[ulIdx];
-		
-		if (entry->bSeparator == true)
-			return; // don't color separators
-	}
-	
-	short dDefault = entry ? entry->col->index () : -1;
-	short dValue;
-	
-	if (ColorSelectDialog::staticDialog (dValue, dDefault, this) == false)
-		return;
-	
-	if (entry)
-		entry->col = getColor (dValue);
-	else {
-		quickColorMetaEntry entry = {getColor (dValue), null, false};
-		
-		item = getSelectedQuickColor ();
-		ulong idx;
-		
-		if (item)
-			idx = getItemRow (item, quickColorItems) + 1;
-		else
-			idx = quickColorItems.size();
-		
-		quickColorMeta.insert (quickColorMeta.begin() + idx, entry);
-		entry = quickColorMeta[idx];
-	}
-	
-	updateQuickColorList (entry);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::slot_delColor () {
-	if (lw_quickColors->selectedItems().size() == 0)
-		return;
-	
-	QListWidgetItem* qItem = lw_quickColors->selectedItems ()[0];
-	ulong ulIdx = getItemRow (qItem, quickColorItems);
-	quickColorMeta.erase (quickColorMeta.begin () + ulIdx);
-	updateQuickColorList ();
-}
-
-// =============================================================================
-void ConfigDialog::slot_moveColor () {
-	const bool bUp = (static_cast<QPushButton*> (sender()) == pb_moveColorUp);
-	
-	if (lw_quickColors->selectedItems().size() == 0)
-		return;
-	
-	QListWidgetItem* qItem = lw_quickColors->selectedItems ()[0];
-	ulong ulIdx = getItemRow (qItem, quickColorItems);
-	
-	long lDest = bUp ? (ulIdx - 1) : (ulIdx + 1);
-	
-	if (lDest < 0 || (ulong)lDest >= quickColorItems.size ())
-		return; // destination out of bounds
-	
-	quickColorMetaEntry tmp = quickColorMeta[lDest];
-	quickColorMeta[lDest] = quickColorMeta[ulIdx];
-	quickColorMeta[ulIdx] = tmp;
-	
-	updateQuickColorList (&quickColorMeta[lDest]);
-}
-
-// =============================================================================
-void ConfigDialog::slot_addColorSeparator() {
-	quickColorMeta.push_back ({null, null, true});
-	updateQuickColorList (&quickColorMeta[quickColorMeta.size () - 1]);
-}
-
-// =============================================================================
-void ConfigDialog::slot_clearColors () {
-	quickColorMeta.clear ();
-	updateQuickColorList ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::makeSlider (QSlider*& slider, short min, short max, short defval) {
-	slider = new QSlider (Qt::Horizontal);
-	slider->setRange (min, max);
-	slider->setSliderPosition (defval);
-	slider->setTickPosition (QSlider::TicksAbove);
-	slider->setTickInterval (1);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-ConfigDialog::~ConfigDialog () {
-	g_ConfigDialog = null;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::pickColor (strconfig& cfg, QPushButton* qButton) {
-	QColorDialog dlg (QColor (cfg.value.chars()));
-	dlg.setWindowIcon (getIcon ("colorselect"));
-	
-	if (dlg.exec ()) {
-		uchar r = dlg.currentColor ().red (),
-			g = dlg.currentColor ().green (),
-			b = dlg.currentColor ().blue ();
-		cfg.value.format ("#%.2X%.2X%.2X", r, g, b);
-		setButtonBackground (qButton, cfg.value);
-	}
-}
-
-void ConfigDialog::slot_setGLBackground () {
-	pickColor (gl_bgcolor, pb_viewBg);
-}
-
-void ConfigDialog::slot_setGLForeground () {
-	pickColor (gl_maincolor, pb_viewFg);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::setButtonBackground (QPushButton* qButton, str zValue) {
-	qButton->setIcon (getIcon ("colorselect"));
-	qButton->setAutoFillBackground (true);
-	qButton->setStyleSheet (
-		fmt ("background-color: %s", zValue.chars()).chars()
-	);
-}
-
-// =============================================================================
-long ConfigDialog::getItemRow (QListWidgetItem* qItem, std::vector<QListWidgetItem*>& haystack) {
-	long i = 0;
-	
-	for (QListWidgetItem* it : haystack) {
-		if (it == qItem)
-			return i;
-		++i;
-	}
-	
-	return -1;
-}
-
-// =============================================================================
-QListWidgetItem* ConfigDialog::getSelectedQuickColor () {
-	if (lw_quickColors->selectedItems().size() == 0)
-		return null;
-	
-	return lw_quickColors->selectedItems ()[0];
-}
-
-// =============================================================================
-QList<ShortcutListItem*> ConfigDialog::getShortcutSelection () {
-	QList<ShortcutListItem*> out;
-	
-	for (QListWidgetItem* entry : lw_shortcutList->selectedItems ())
-		out << static_cast<ShortcutListItem*> (entry);
-	
-	return out;
-}
-
-// =============================================================================
-void ConfigDialog::slot_setShortcut () {
-	QList<ShortcutListItem*> sel = getShortcutSelection ();
-	
-	if (sel.size() < 1)
-		return;
-	
-	ShortcutListItem* item = sel[0];
-	if (KeySequenceDialog::staticDialog (*(item->getActionInfo ()), this))
-		setShortcutText (item, *(item->getActionInfo ()));
-}
-
-// =============================================================================
-void ConfigDialog::slot_resetShortcut () {
-	QList<ShortcutListItem*> sel = getShortcutSelection ();
-	
-	for (ShortcutListItem* item : sel) {
-		actionmeta* info = item->getActionInfo ();
-		keyseqconfig* conf = info->conf;
-		
-		conf->reset ();
-		(*info->qAct)->setShortcut (*conf);
-		
-		setShortcutText (item, *info);
-	}
-}
-
-// =============================================================================
-void ConfigDialog::slot_clearShortcut () {
-	QList<ShortcutListItem*> sel = getShortcutSelection ();
-	QKeySequence dummy;
-	
-	for (ShortcutListItem* item : sel) {
-		actionmeta* info = item->getActionInfo ();
-		keyseqconfig* conf = info->conf;
-		conf->value = dummy;
-		
-		(*info->qAct)->setShortcut (*conf);
-		setShortcutText (item, *info);
-	}
-}
-
-// =============================================================================
-void ConfigDialog::slot_setExtProgPath () {
-	const extProgInfo* info = null;
-	for (const extProgInfo& it : g_extProgInfo) {
-		if (it.setPathButton == sender ()) {
-			info = &it;
-			break;
-		}
-	}
-	
-	assert (info != null);
-	
-	str filter;
-#ifdef _WIN32
-	filter = "Applications (*.exe)(*.exe);;All files (*.*)(*.*)";
-#endif // WIN32
-	
-	str fpath = QFileDialog::getOpenFileName (this, fmt ("Path to %s", info->name), "", filter);
-	if (!~fpath)
-		return;
-	
-	info->input->setText (fpath);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::setShortcutText (QListWidgetItem* qItem, actionmeta meta) {
-	QAction* const act = *meta.qAct;
-	str zLabel = act->iconText ();
-	str zKeybind = act->shortcut ().toString ();
-	
-	qItem->setText (fmt ("%s (%s)", zLabel.chars () ,zKeybind.chars ()).chars());
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-str ConfigDialog::makeColorToolBarString () {
-	str val;
-	
-	for (quickColorMetaEntry entry : quickColorMeta) {
-		if (~val > 0)
-			val += ':';
-		
-		if (entry.bSeparator)
-			val += '|';
-		else
-			val.appendformat ("%d", entry.col->index ());
-	}
-	
-	return val;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void ConfigDialog::staticDialog () {
-	ConfigDialog dlg (g_win);
-	
-	if (dlg.exec ()) {
-		lv_colorize = dlg.cb_colorize->isChecked ();
-		gl_colorbfc = dlg.cb_colorBFC->isChecked ();
-		gl_selflash = dlg.cb_selFlash->isChecked ();
-		edit_schemanticinline = dlg.cb_schemanticInline->isChecked ();
-		gl_blackedges = dlg.cb_blackEdges->isChecked ();
-		
-		gl_maincolor_alpha = ((double)dlg.sl_viewFgAlpha->value ()) / 10.0f;
-		gl_linethickness = dlg.sl_lineThickness->value ();
-		gui_toolbar_iconsize = (dlg.sl_iconSize->value () * 4) + 12;
-		
-		// Manage the quick color toolbar
-		g_win->setQuickColorMeta (dlg.quickColorMeta);
-		gui_colortoolbar = dlg.makeColorToolBarString ();
-		
-		// Set the grid settings
-		for (int i = 0; i < g_NumGrids; ++i)
-			for (int j = 0; j < 4; ++j)
-				g_GridInfo[i].confs[j]->value = dlg.dsb_gridData[i][j]->value ();
-		
-		// Ext program settings
-		for (const extProgInfo& info : g_extProgInfo)
-			*info.path = info.input->text ();
-		
-		// Save the config
-		config::save ();
-		
-		// Reload all subfiles as the ldraw path potentially changed.
-		reloadAllSubfiles ();
-		
-		g_win->R ()->setBackground ();
-		g_win->refresh ();
-		g_win->updateToolBars ();
-	}
-}
-
-// =========================================================================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =========================================================================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =========================================================================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =========================================================================================================================
-KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent,
-	Qt::WindowFlags f) : QDialog (parent, f), seq (seq)
-{
-	lb_output = new QLabel;
-	IMPLEMENT_DIALOG_BUTTONS
-	
-	setWhatsThis ("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 (actionmeta& meta, QWidget* parent) {
-	KeySequenceDialog dlg (*meta.conf, parent);
-	
-	if (dlg.exec () == false)
-		return false;
-	
-	*meta.conf = dlg.seq;
-	(*meta.qAct)->setShortcut (*meta.conf);
-	return true;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void KeySequenceDialog::updateOutput () {
-	str zShortcut = seq.toString ();
-	
-	str zText = fmt ("<center><b>%s</b></center>", zShortcut.chars ());
-	
-	lb_output->setText (zText);
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) {
-	seq = ev->key ();
-	
-	switch (seq) {
-	case Qt::Key_Shift:
-	case Qt::Key_Control:
-	case Qt::Key_Alt:
-	case Qt::Key_Meta:
-		seq = 0;
-		break;
-	
-	default:
-		break;
-	}
-	
-	seq = (seq | ev->modifiers ());
-	
-	updateOutput ();
-}
\ No newline at end of file
--- a/zz_configDialog.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef CONFIGDIALOG_H
-#define CONFIGDIALOG_H
-
-#include "gui.h"
-#include <qdialog.h>
-#include <qlabel.h>
-#include <qlineedit.h>
-#include <qdialogbuttonbox.h>
-#include <qpushbutton.h>
-#include <qcheckbox.h>
-#include <qlistwidget.h>
-#include <qspinbox.h>
-
-// =============================================================================
-class ShortcutListItem : public QListWidgetItem {
-public:
-	explicit ShortcutListItem (QListWidget* view = null, int type = Type) :
-		QListWidgetItem (view, type) {}
-	
-	actionmeta* getActionInfo () const { return m_info; }
-	void setActionInfo (actionmeta* info) { m_info = info; }
-	
-private:
-	actionmeta* m_info;
-};
-
-// =============================================================================
-class ConfigDialog : public QDialog {
-	Q_OBJECT
-	
-public:
-	QTabWidget* tabs;
-	QWidget* mainTab, *shortcutsTab, *quickColorTab, *extProgTab;
-	
-	// =========================================================================
-	// Main tab widgets
-	QLabel* lb_viewBg, *lb_viewFg, *lb_viewFgAlpha;
-	QLabel* lb_lineThickness, *lb_iconSize;
-	QPushButton* pb_viewBg, *pb_viewFg;
-	QCheckBox* cb_colorize, *cb_colorBFC, *cb_selFlash, *cb_schemanticInline,
-		*cb_blackEdges;
-	QSlider* sl_viewFgAlpha, *sl_lineThickness, *sl_iconSize;
-	
-	// =========================================================================
-	// Shortcuts tab
-	QListWidget* lw_shortcutList;
-	QPushButton* pb_setShortcut, *pb_resetShortcut, *pb_clearShortcut;
-	
-	// =========================================================================
-	// Quick color toolbar tab
-	QListWidget* lw_quickColors;
-	QPushButton* pb_addColor, *pb_delColor, *pb_changeColor, *pb_addColorSeparator,
-		*pb_moveColorUp, *pb_moveColorDown, *pb_clearColors;
-	std::vector<QListWidgetItem*> quickColorItems;
-	std::vector<quickColorMetaEntry> quickColorMeta;
-	
-	// =========================================================================
-	// Grid tab
-	QLabel* lb_gridLabels[3];
-	QLabel* lb_gridIcons[3];
-	QDoubleSpinBox* dsb_gridData[3][4];
-	
-	// =========================================================================
-	QDialogButtonBox* bbx_buttons;
-	
-	ConfigDialog (ForgeWindow* parent);
-	~ConfigDialog ();
-	static void staticDialog ();
-	
-private:
-	void initMainTab ();
-	void initShortcutsTab ();
-	void initQuickColorTab ();
-	void initGridTab ();
-	void initExtProgTab ();
-	
-	void makeSlider (QSlider*& slider, short min, short max, short defval);
-	void setButtonBackground (QPushButton* qButton, str zValue);
-	void pickColor (strconfig& cfg, QPushButton* qButton);
-	void updateQuickColorList (quickColorMetaEntry* pSel = null);
-	void setShortcutText (QListWidgetItem* qItem, actionmeta meta);
-	long getItemRow (QListWidgetItem* qItem, std::vector<QListWidgetItem*>& haystack);
-	str makeColorToolBarString ();
-	QListWidgetItem* getSelectedQuickColor ();
-	QList<ShortcutListItem*> getShortcutSelection ();
-	
-private slots:
-	void slot_setGLBackground ();
-	void slot_setGLForeground ();
-	
-	void slot_setShortcut ();
-	void slot_resetShortcut ();
-	void slot_clearShortcut ();
-	
-	void slot_setColor ();
-	void slot_delColor ();
-	void slot_addColorSeparator ();
-	void slot_moveColor ();
-	void slot_clearColors ();
-	
-	void slot_setExtProgPath ();
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-class KeySequenceDialog : public QDialog {
-	Q_OBJECT
-	
-public:
-	explicit KeySequenceDialog (QKeySequence seq, QWidget* parent = null, Qt::WindowFlags f = 0);
-	static bool staticDialog (actionmeta& meta, QWidget* parent = null);
-	
-	QLabel* lb_output;
-	QDialogButtonBox* bbx_buttons;
-	QKeySequence seq;
-	
-private:
-	void updateOutput ();
-	
-private slots:
-	virtual void keyPressEvent (QKeyEvent* ev);
-};
-
-#endif // CONFIGDIALOG_H
\ No newline at end of file
--- a/zz_historyDialog.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,283 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "zz_historyDialog.h"
-#include "history.h"
-#include "colors.h"
-#include <qboxlayout.h>
-#include <qmessagebox.h>
-
-EXTERN_ACTION (undo);
-EXTERN_ACTION (redo);
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-HistoryDialog::HistoryDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) {
-	historyList = new QListWidget;
-	undoButton = new QPushButton ("Undo");
-	redoButton = new QPushButton ("Redo");
-	clearButton = new QPushButton ("Clear");
-	buttons = new QDialogButtonBox (QDialogButtonBox::Close);
-	
-	historyList->setAlternatingRowColors (true);
-	
-	undoButton->setIcon (getIcon ("undo"));
-	redoButton->setIcon (getIcon ("redo"));
-	
-	connect (undoButton, SIGNAL (clicked ()), this, SLOT (slot_undo ()));
-	connect (redoButton, SIGNAL (clicked ()), this, SLOT (slot_redo ()));
-	connect (clearButton, SIGNAL (clicked ()), this, SLOT (slot_clear ()));
-	connect (buttons, SIGNAL (rejected ()), this, SLOT (reject ()));
-	connect (historyList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_selChanged ()));
-	
-	QVBoxLayout* qButtonLayout = new QVBoxLayout;
-	qButtonLayout->setDirection (QBoxLayout::TopToBottom);
-	qButtonLayout->addWidget (undoButton);
-	qButtonLayout->addWidget (redoButton);
-	qButtonLayout->addWidget (clearButton);
-	qButtonLayout->addStretch ();
-	
-	QGridLayout* qLayout = new QGridLayout;
-	qLayout->setColumnStretch (0, 1);
-	qLayout->addWidget (historyList, 0, 0, 2, 1);
-	qLayout->addLayout (qButtonLayout, 0, 1);
-	qLayout->addWidget (buttons, 1, 1);
-	
-	setLayout (qLayout);
-	setWindowIcon (getIcon ("history"));
-	setWindowTitle (APPNAME " - Edit history");
-	
-	populateList ();
-	updateButtons ();
-	updateSelection ();
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void HistoryDialog::populateList () {
-	historyList->clear ();
-	
-	QListWidgetItem* qItem = new QListWidgetItem;
-	qItem->setText ("[[ initial state ]]");
-	qItem->setIcon (getIcon ("empty"));
-	historyList->addItem (qItem);
-	
-	for (HistoryEntry* entry : History::entries ()) {
-		str text;
-		QIcon entryIcon;
-		
-		switch (entry->type ()) {
-		case HISTORY_Add:
-			{
-				AddHistory* subentry = static_cast<AddHistory*> (entry);
-				ulong count = subentry->paObjs.size ();
-				str verb = "Added";
-				
-				switch (subentry->eType) {
-				case AddHistory::Paste:
-					verb = "Pasted";
-					entryIcon = getIcon ("paste");
-					break;
-				
-				default:
-					{
-						// Determine a common type for these objects. If all objects are of the same
-						// type, we display its addition icon. Otherwise, we draw a subfile addition
-						// one as a default.
-						LDObject::Type eCommonType = LDObject::Unidentified;
-						for (LDObject* obj : subentry->paObjs) {
-							if (eCommonType == LDObject::Unidentified or obj->getType() == eCommonType)
-								eCommonType = obj->getType ();
-							else {
-								eCommonType = LDObject::Unidentified;
-								break;
-							}
-						}
-						
-						// Set the icon based on the common type decided above.
-						if (eCommonType == LDObject::Unidentified)
-							entryIcon = getIcon ("add-subfile");
-						else
-							entryIcon = getIcon (fmt ("add-%s", g_saObjTypeIcons[eCommonType]));
-					}
-					break;
-				}
-				
-				text.format ("%s %lu objects\n%s", verb.chars(), count,
-					LDObject::objectListContents (subentry->paObjs).chars());
-			}
-			break;
-		
-		case HISTORY_QuadSplit:
-			{
-				QuadSplitHistory* subentry = static_cast<QuadSplitHistory*> (entry);
-				ulong ulCount = subentry->paQuads.size ();
-				text.format ("Split %lu quad%s to triangles", ulCount, PLURAL (ulCount));
-				
-				entryIcon = getIcon ("quad-split");
-			}
-			break;
-		
-		case HISTORY_Del:
-			{
-				DelHistory* subentry = static_cast<DelHistory*> (entry);
-				ulong count = subentry->cache.size ();
-				str verb = "Deleted";
-				entryIcon = getIcon ("delete");
-				
-				switch (subentry->eType) {
-				case DelHistory::Cut:
-					entryIcon = getIcon ("cut");
-					verb = "Cut";
-					break;
-				
-				default:
-					break;
-				}
-				
-				text.format ("%s %lu objects:\n%s", verb.chars(), count,
-					LDObject::objectListContents (subentry->cache).chars ());
-			}
-			break;
-		
-		case HISTORY_SetColor:
-			{
-				SetColorHistory* subentry = static_cast<SetColorHistory*> (entry);
-				ulong count = subentry->ulaIndices.size ();
-				text.format ("Set color of %lu objects to %d (%s)", count,
-					subentry->dNewColor, getColor (subentry->dNewColor)->zName.chars());
-				
-				entryIcon = getIcon ("palette");
-			}
-			break;
-		
-		case HISTORY_ListMove:
-			{
-				ListMoveHistory* subentry = static_cast<ListMoveHistory*> (entry);
-				ulong ulCount = subentry->ulaIndices.size ();
-				
-				text.format ("Moved %lu objects %s", ulCount,
-					subentry->bUp ? "up" : "down");
-				entryIcon = getIcon (subentry->bUp ? "arrow-up" : "arrow-down");
-			}
-			break;
-		
-		case HISTORY_Edit:
-			{
-				EditHistory* subentry = static_cast<EditHistory*> (entry);
-				
-				text.format ("Edited %u objects\n%s",
-					subentry->paNewObjs.size(),
-					LDObject::objectListContents (subentry->paOldObjs).chars ());
-				entryIcon = getIcon ("set-contents");
-			}
-			break;
-		
-		case HISTORY_Inline:
-			{
-				InlineHistory* subentry = static_cast<InlineHistory*> (entry);
-				
-				text.format ("%s %lu subfiles:\n%lu resultants",
-					(subentry->bDeep) ? "Deep-inlined" : "Inlined",
-					(ulong) subentry->paRefs.size(),
-					(ulong) subentry->ulaBitIndices.size());
-				
-				entryIcon = getIcon (subentry->bDeep ? "inline-deep" : "inline");
-			}
-			break;
-		
-		default:
-			text = "???";
-			break;
-		}
-		
-		QListWidgetItem* item = new QListWidgetItem;
-		item->setText (text);
-		item->setIcon (entryIcon);
-		historyList->addItem (item);
-	}
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void HistoryDialog::slot_undo () {
-	History::undo ();
-	updateButtons ();
-	updateSelection ();
-}
-
-// =============================================================================
-void HistoryDialog::slot_redo () {
-	History::redo ();
-	updateButtons ();
-	updateSelection ();
-}
-
-void HistoryDialog::updateSelection () {
-	historyList->setCurrentItem (historyList->item (History::pos () + 1));
-}
-
-// =============================================================================
-void HistoryDialog::slot_clear () {
-	if (!confirm ("Are you sure you want to clear the edit history?\nThis cannot be undone."))
-		return; // Canceled
-	
-	History::clear ();
-	populateList ();
-	updateButtons ();
-}
-
-// =============================================================================
-void HistoryDialog::updateButtons () {
-	undoButton->setEnabled (ACTION (undo)->isEnabled ());
-	redoButton->setEnabled (ACTION (redo)->isEnabled ());
-}
-
-// =============================================================================
-void HistoryDialog::slot_selChanged () {
-	if (historyList->selectedItems ().size () != 1)
-		return;
-	
-	QListWidgetItem* qItem = historyList->selectedItems ()[0];
-	
-	// Find the index of the edit
-	long lIdx;
-	for (lIdx = 0; lIdx < historyList->count (); ++lIdx)
-		if (historyList->item (lIdx) == qItem)
-			break;
-	
-	// qHistoryList is 0-based, History is -1-based, thus decrement
-	// the index now
-	lIdx--;
-	
-	if (lIdx == History::pos ())
-		return;
-	
-	// Seek to the selected edit by repeadetly undoing or redoing.
-	while (History::pos () != lIdx) {
-		if (History::pos () > lIdx)
-			History::undo ();
-		else
-			History::redo ();
-	}
-	
-	updateButtons ();
-}
\ No newline at end of file
--- a/zz_historyDialog.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef HISTORYDIALOG_H
-#define HISTORYDIALOG_H
-
-#include <qdialog.h>
-#include <qdialogbuttonbox.h>
-#include <qlistwidget.h>
-#include <qpushbutton.h>
-#include "gui.h"
-
-class HistoryDialog : public QDialog {
-	Q_OBJECT
-	
-public:
-	explicit HistoryDialog (QWidget* parent = null, Qt::WindowFlags f = 0);
-	void populateList ();
-	
-	QListWidget* historyList;
-	QPushButton* undoButton, *redoButton, *clearButton;
-	QDialogButtonBox* buttons;
-	
-private:
-	void updateButtons ();
-	void updateSelection ();
-	
-private slots:
-	void slot_undo ();
-	void slot_redo ();
-	void slot_clear ();
-	void slot_selChanged ();
-};
-
-#endif // HISTORYDIALOG_H
\ No newline at end of file
--- a/zz_ldrawPathDialog.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <qlineedit.h>
-#include <qpushbutton.h>
-#include <qdialogbuttonbox.h>
-#include <QFileDialog>
-#include "zz_ldrawPathDialog.h"
-#include "gui.h"
-#include "file.h"
-
-extern_cfg (str, io_ldpath);
-
-// ========================================================================================================================================
-LDrawPathDialog::LDrawPathDialog (const bool validDefault, QWidget* parent, Qt::WindowFlags f)
-	: QDialog (parent, f), m_validDefault (validDefault)
-{
-	QLabel* lb_description;
-	lb_resolution = new QLabel ("---");
-	
-	if (validDefault == false)
-		lb_description = new QLabel ("Please input your LDraw directory");
-	
-	QLabel* lb_path = new QLabel ("LDraw path:");
-	le_path = new QLineEdit;
-	btn_findPath = new QPushButton;
-	btn_findPath->setIcon (getIcon ("folder"));
-	
-	btn_tryConfigure = new QPushButton ("Configure");
-	btn_tryConfigure->setIcon (getIcon ("settings"));
-	
-	btn_cancel = new QPushButton;
-	
-	if (validDefault == false) {
-		btn_cancel->setText ("Exit");
-		btn_cancel->setIcon (getIcon ("exit"));
-	} else {
-		btn_cancel->setText ("Cancel");
-		btn_cancel->setIcon (getIcon ("cancel"));
-	}
-	
-	dbb_buttons = new QDialogButtonBox (QDialogButtonBox::Ok);
-	dbb_buttons->addButton (btn_tryConfigure, QDialogButtonBox::ActionRole);
-	dbb_buttons->addButton (btn_cancel, QDialogButtonBox::RejectRole);
-	okButton ()->setEnabled (false);
-	
-	QHBoxLayout* inputLayout = new QHBoxLayout;
-	inputLayout->addWidget (lb_path);
-	inputLayout->addWidget (le_path);
-	inputLayout->addWidget (btn_findPath);
-	
-	QVBoxLayout* mainLayout = new QVBoxLayout;
-	
-	if (validDefault == false)
-		mainLayout->addWidget (lb_description);
-	
-	mainLayout->addLayout (inputLayout);
-	mainLayout->addWidget (lb_resolution);
-	mainLayout->addWidget (dbb_buttons);
-	setLayout (mainLayout);
-	
-	connect (le_path, SIGNAL (textEdited ()), this, SLOT (slot_tryConfigure ()));
-	connect (btn_findPath, SIGNAL (clicked ()), this, SLOT (slot_findPath ()));
-	connect (btn_tryConfigure, SIGNAL (clicked ()), this, SLOT (slot_tryConfigure ()));
-	connect (dbb_buttons, SIGNAL (accepted ()), this, SLOT (accept ()));
-	connect (dbb_buttons, SIGNAL (rejected ()), this, (validDefault) ? SLOT (reject ()) : SLOT (slot_exit ()));
-	
-	setPath (io_ldpath);
-	if (validDefault)
-		slot_tryConfigure ();
-}
-
-// ========================================================================================================================================
-QPushButton* LDrawPathDialog::okButton () {
-	return dbb_buttons->button (QDialogButtonBox::Ok);
-}
-
-// ========================================================================================================================================
-void LDrawPathDialog::setPath (str path) {
-	le_path->setText (path);
-}
-
-// ========================================================================================================================================
-str LDrawPathDialog::path () const {
-	return le_path->text ();
-}
-
-// ========================================================================================================================================
-void LDrawPathDialog::slot_findPath () {
-	str newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path");
-	
-	if (~newpath > 0 && newpath != path ()) {
-		setPath (newpath);
-		slot_tryConfigure ();
-	}
-}
-
-
-// ========================================================================================================================================
-void LDrawPathDialog::slot_exit () {
-	exit (1);
-}
-
-// ========================================================================================================================================
-void LDrawPathDialog::slot_tryConfigure () {
-	if (LDPaths::tryConfigure (path ()) == false) {
-		lb_resolution->setText (fmt ("<span style=\"color:red; font-weight: bold;\">%s</span>", LDPaths::getError().chars ()));
-		okButton ()->setEnabled (false);
-		return;
-	}
-	
-	lb_resolution->setText ("<span style=\"color: #7A0; font-weight: bold;\">OK!</span>");
-	okButton ()->setEnabled (true);
-}
\ No newline at end of file
--- a/zz_ldrawPathDialog.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef LDRAWPATHDIALOG_H
-#define LDRAWPATHDIALOG_H
-
-#include <qdialog.h>
-#include "common.h"
-
-class QLabel;
-class QLineEdit;
-class QDialogButtonBox;
-
-class LDrawPathDialog : public QDialog {
-	Q_OBJECT
-	
-public:
-	explicit LDrawPathDialog (const bool validDefault, QWidget* parent = null, Qt::WindowFlags f = 0);
-	str path () const;
-	void setPath (str path);
-	void (*callback ()) () const { return m_callback; }
-	void setCallback (void (*callback) ()) { m_callback = callback; }
-	
-private:
-	Q_DISABLE_COPY (LDrawPathDialog)
-	
-	QLabel* lb_resolution;
-	QLineEdit* le_path;
-	QPushButton* btn_findPath, *btn_tryConfigure, *btn_cancel;
-	QDialogButtonBox* dbb_buttons;
-	void (*m_callback) ();
-	const bool m_validDefault;
-	
-	QPushButton* okButton ();
-	
-private slots:
-	void slot_findPath ();
-	void slot_tryConfigure ();
-	void slot_exit ();
-};
-
-#endif // LDRAWPATHDIALOG_H
--- a/zz_newPartDialog.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <qgridlayout.h>
-#include "zz_newPartDialog.h"
-#include "file.h"
-
-// -------------------------------------
-enum {
-	LICENSE_CCAL,
-	LICENSE_NonCA,
-	LICENSE_None
-};
-
-// -------------------------------------
-enum {
-	BFCBOX_CCW,
-	BFCBOX_CW,
-	BFCBOX_None,
-};
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-NewPartDialog::NewPartDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) {
-	lb_brickIcon = new QLabel;
-	lb_brickIcon->setPixmap (getIcon ("brick"));
-	
-	lb_name = new QLabel ("Name:");
-	le_name = new QLineEdit;
-	le_name->setMinimumWidth (320);
-	
-	lb_author = new QLabel ("Author:");
-	le_author = new QLineEdit;
-	
-	rb_license = new RadioBox ("License", {
-		"CCAL Redistributable",
-		"Non-redistributable",
-		"Don't append a license",
-	}, LICENSE_CCAL);
-	
-	rb_BFC = new RadioBox ("BFC Winding", {
-		"CCW",
-		"CW",
-		"No winding"
-	}, BFCBOX_CCW);
-	
-	QHBoxLayout* boxes = new QHBoxLayout;
-	boxes->addWidget (rb_license);
-	boxes->addWidget (rb_BFC);
-	
-	IMPLEMENT_DIALOG_BUTTONS
-	
-	QGridLayout* layout = new QGridLayout;
-	layout->addWidget (lb_brickIcon, 0, 0);
-	layout->addWidget (lb_name, 0, 1);
-	layout->addWidget (le_name, 0, 2);
-	layout->addWidget (lb_author, 1, 1);
-	layout->addWidget (le_author, 1, 2);
-	layout->addLayout (boxes, 2, 1, 1, 2);
-	layout->addWidget (bbx_buttons, 3, 2);
-	
-	setLayout (layout);
-	setWindowIcon (getIcon ("brick"));
-	setWindowTitle (APPNAME ": New Part");
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void NewPartDialog::StaticDialog () {
-	NewPartDialog dlg (g_win);
-	if (dlg.exec ()) {
-		newFile ();
-		
-		short idx;
-		str zAuthor = dlg.le_author->text ();
-		vector<LDObject*>& objs = g_curfile->m_objs;
-		
-		idx = dlg.rb_BFC->value ();
-		const LDBFC::Type eBFCType =
-			(idx == BFCBOX_CCW) ? LDBFC::CertifyCCW :
-			(idx == BFCBOX_CW) ? LDBFC::CertifyCW :
-			LDBFC::NoCertify;
-		
-		idx = dlg.rb_license->value ();
-		const char* sLicense =
-			(idx == LICENSE_CCAL) ? "Redistributable under CCAL version 2.0 : see CAreadme.txt" :
-			(idx == LICENSE_NonCA) ? "Not redistributable : see NonCAreadme.txt" :
-			null;
-		
-		objs.push_back (new LDComment (dlg.le_name->text ()));
-		objs.push_back (new LDComment ("Name: <untitled>.dat"));
-		objs.push_back (new LDComment (fmt ("Author: %s", zAuthor.chars())));
-		objs.push_back (new LDComment (fmt ("!LDRAW_ORG Unofficial_Part")));
-		
-		if (sLicense != null)
-			objs.push_back (new LDComment (fmt ("!LICENSE %s", sLicense)));
-		
-		objs.push_back (new LDEmpty);
-		objs.push_back (new LDBFC (eBFCType));
-		objs.push_back (new LDEmpty);
-		
-		g_win->refresh ();
-	}
-}
\ No newline at end of file
--- a/zz_newPartDialog.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef NEWPARTDIALOG_H
-#define NEWPARTDIALOG_H
-
-#include <qdialog.h>
-#include <qlabel.h>
-#include <qlineedit.h>
-#include <qcombobox.h>
-#include <qdialogbuttonbox.h>
-#include <qradiobutton.h>
-#include <qbuttongroup.h>
-#include "gui.h"
-#include "radiobox.h"
-
-class NewPartDialog : public QDialog {
-public:
-	explicit NewPartDialog (QWidget* parent = null, Qt::WindowFlags f = 0);
-	static void StaticDialog ();
-	
-	QLabel* lb_brickIcon, *lb_name, *lb_author, *lb_license, *lb_BFC;
-	QLineEdit* le_name, *le_author;
-	
-	RadioBox* rb_license, *rb_BFC;
-	QDialogButtonBox* bbx_buttons;
-};
-
-#endif // NEWPARTDIALOG_H
\ No newline at end of file
--- a/zz_setContentsDialog.cpp	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QAbstractButton>
-#include <qboxlayout.h>
-#include "zz_setContentsDialog.h"
-#include "file.h"
-#include "gui.h"
-#include "history.h"
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-SetContentsDialog::SetContentsDialog (LDObject* obj, QWidget* parent) : QDialog(parent) {
-	setWindowTitle (APPNAME ": Set Contents");
-	
-	lb_contents = new QLabel ("Set contents:", parent);
-	
-	le_contents = new QLineEdit (parent);
-	le_contents->setText (obj->getContents ().chars());
-	le_contents->setWhatsThis ("The LDraw code of this object. The code written "
-		"here is expected to be valid LDraw code, invalid code here results "
-		"the object being turned into an error object. Please do refer to the "
-		"<a href=\"http://www.ldraw.org/article/218.html\">official file format "
-		"standard</a> for further information.");
-	le_contents->setMinimumWidth (384);
-	
-	if (obj->getType() == LDObject::Gibberish) {
-		lb_error = new QLabel;
-		lb_error->setText (fmt ("<span style=\"color: #900\">%s</span>",
-			static_cast<LDGibberish*> (obj)->zReason.chars()));
-		
-		QPixmap qErrorPixmap = getIcon ("error").scaledToHeight (16);
-		
-		lb_errorIcon = new QLabel;
-		lb_errorIcon->setPixmap (qErrorPixmap);
-	}
-	
-	IMPLEMENT_DIALOG_BUTTONS
-	
-	QVBoxLayout* layout = new QVBoxLayout;
-	layout->addWidget (lb_contents);
-	layout->addWidget (le_contents);
-	
-	QHBoxLayout* layout2 = new QHBoxLayout;
-	
-	if (obj->getType() == LDObject::Gibberish) {
-		layout2->addWidget (lb_errorIcon);
-		layout2->addWidget (lb_error);
-	}
-	
-	layout2->addWidget (bbx_buttons);
-	layout->addLayout (layout2);
-	setLayout (layout);
-	
-	setWindowTitle (APPNAME ": Set Contents");
-	setWindowIcon (getIcon ("set-contents"));
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void SetContentsDialog::slot_handleButtons (QAbstractButton* qButton) {
-	qButton = qButton;
-}
-
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
-void SetContentsDialog::staticDialog (LDObject* obj) {
-	if (!obj)
-		return;
-	
-	SetContentsDialog dlg (obj, g_win);
-	if (dlg.exec () == false)
-		return;
-	
-	LDObject* oldobj = obj;
-	
-	// Reinterpret it from the text of the input field
-	obj = parseLine (dlg.le_contents->text ().toStdString ().c_str ());
-	
-	// Mark down the history now before we perform the replacement (which
-	// destroys the old object)
-	History::addEntry (new EditHistory ({(ulong) oldobj->getIndex (g_curfile)},
-		{oldobj->clone ()}, {obj->clone ()}));
-	
-	oldobj->replace (obj);
-	
-	// Rebuild stuff after this
-	g_win->refresh ();
-}
\ No newline at end of file
--- a/zz_setContentsDialog.h	Wed May 08 14:57:48 2013 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef SETCONTENTSDIALOG_H
-
-#include <qdialog.h>
-#include <qlineedit.h>
-#include <qlabel.h>
-#include <qdialogbuttonbox.h>
-#include "common.h"
-
-// =============================================================================
-// SetContentsDialog
-//
-// Performs the Set Contents dialog on the given LDObject. Object's contents
-// are exposed to the user and is reinterpreted if the user accepts the new
-// contents.
-// =============================================================================
-class SetContentsDialog : public QDialog {
-public:
-	QLabel* lb_contents, *lb_errorIcon, *lb_error;
-	QLineEdit* le_contents;
-	QDialogButtonBox* bbx_buttons;
-	
-	SetContentsDialog (LDObject* obj, QWidget* parent = null);
-	static void staticDialog (LDObject* obj);
-	
-private slots:
-	void slot_handleButtons (QAbstractButton* qButton);
-};
-
-#endif // SETCONTENTSDIALOG_H
\ No newline at end of file

mercurial