Initial commit

Fri, 15 Mar 2013 20:11:18 +0200

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Fri, 15 Mar 2013 20:11:18 +0200
changeset 0
c51cce84a9ac
child 1
001d6cfb2d86

Initial commit

bbox.cpp file | annotate | diff | comparison | revisions
bbox.h file | annotate | diff | comparison | revisions
cfgdef.h file | annotate | diff | comparison | revisions
color.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
draw.cpp file | annotate | diff | comparison | revisions
draw.h file | annotate | diff | comparison | revisions
gui.cpp file | annotate | diff | comparison | revisions
gui.h file | annotate | diff | comparison | revisions
icons/.directory file | annotate | diff | comparison | revisions
icons/add-line.png file | annotate | diff | comparison | revisions
icons/add-quad.png file | annotate | diff | comparison | revisions
icons/add-subfile.png file | annotate | diff | comparison | revisions
icons/add-triangle.png file | annotate | diff | comparison | revisions
icons/add-vector.png file | annotate | diff | comparison | revisions
icons/copy.png file | annotate | diff | comparison | revisions
icons/cut.png file | annotate | diff | comparison | revisions
icons/del-line.png file | annotate | diff | comparison | revisions
icons/empty.png file | annotate | diff | comparison | revisions
icons/error.png file | annotate | diff | comparison | revisions
icons/file-new.png file | annotate | diff | comparison | revisions
icons/file.png file | annotate | diff | comparison | revisions
icons/line.png file | annotate | diff | comparison | revisions
icons/paste.png file | annotate | diff | comparison | revisions
icons/quad.png file | annotate | diff | comparison | revisions
icons/save-as.png file | annotate | diff | comparison | revisions
icons/save.png file | annotate | diff | comparison | revisions
icons/subfile.png file | annotate | diff | comparison | revisions
icons/triangle.png file | annotate | diff | comparison | revisions
icons/vector.png file | annotate | diff | comparison | revisions
io.cpp file | annotate | diff | comparison | revisions
io.h file | annotate | diff | comparison | revisions
ldforge.kdev4 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
moc_draw.cpp file | annotate | diff | comparison | revisions
moc_gui.cpp file | annotate | diff | comparison | revisions
moc_ldforge.cpp file | annotate | diff | comparison | revisions
model.cpp file | annotate | diff | comparison | revisions
model.h file | annotate | diff | comparison | revisions
scanner.cpp file | annotate | diff | comparison | revisions
scanner.h file | annotate | diff | comparison | revisions
str.cpp file | annotate | diff | comparison | revisions
str.h file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bbox.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,66 @@
+#include "common.h"
+#include "bbox.h"
+#include "ldtypes.h"
+
+void bbox::calculate () {
+	// The bounding box, bbox for short, is the
+	// box that encompasses the model we have open.
+	// v0 is the minimum vertex, v1 is the maximum vertex.
+	for (uint i = 0; i < g_CurrentFile->objects.size(); i++) {
+		LDObject* obj = g_CurrentFile->objects[i];
+		switch (obj->getType ()) {
+		
+		case OBJ_Line:
+			{
+				LDLine* line = static_cast<LDLine*> (obj);
+				for (short i = 0; i < 2; ++i)
+					checkVertex (line->vaCoords[i]);
+			}
+			break;
+		
+		case OBJ_Triangle:
+			{
+				LDTriangle* tri = static_cast<LDTriangle*> (obj);
+				for (short i = 0; i < 3; ++i)
+					checkVertex (tri->vaCoords[i]);
+			}
+			break;
+		
+		case OBJ_Quad:
+			{
+				LDQuad* quad = static_cast<LDQuad*> (obj);
+				for (short i = 0; i < 4; ++i)
+					checkVertex (quad->vaCoords[i]);
+			}
+			break;
+		
+		case OBJ_CondLine:
+			{
+				LDCondLine* line = static_cast<LDCondLine*> (obj);
+				for (short i = 0; i < 2; ++i) {
+					checkVertex (line->vaCoords[i]);
+					checkVertex (line->vaControl[i]);
+				}
+			}
+			break;
+		
+		default:
+			break;
+		}
+	}
+}
+
+#define CHECK_DIMENSION(V,X) \
+	if (V.X < v0.X) v0.X = V.X; \
+	if (V.X > v1.X) v1.X = V.X;
+void bbox::checkVertex (vertex v) {
+	CHECK_DIMENSION (v, x)
+	CHECK_DIMENSION (v, y)
+	CHECK_DIMENSION (v, z)
+}
+#undef CHECK_DIMENSION
+
+bbox::bbox () {
+	memset (&v0, 0, sizeof v0);
+	memset (&v1, 0, sizeof v1);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bbox.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,18 @@
+#ifndef __BBOX_H__
+#define __BBOX_H__
+
+#include "common.h"
+
+class bbox {
+public:
+	vertex v0;
+	vertex v1;
+	
+	bbox ();
+	void calculate ();
+	
+private:
+	void checkVertex (vertex v);
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cfgdef.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,6 @@
+// SECT (prefix, Display name)
+// CFG (type, prefix, name, description, default)
+
+// ---------------------------------------------------------
+SECT (io, Files)
+CFG (str, io, openfile, "Current open fle", "box5.dat")
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/color.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,75 @@
+#ifndef __COLOR_H__
+#define __COLOR_H__
+
+#include <stdint.h>
+
+#ifdef QT_VERSION
+ #include <QColor>
+#endif // QT_VERSION
+
+class color {
+public:
+	unsigned char r, g, b;
+	
+	color () {}
+	color (const char* other) {
+		parseFromString (str (other));
+	}
+	
+	bool parseFromString (str in) {
+#ifdef QT_VERSION
+		// Use Qt's method for a quick color check. Handles
+		// named colors, too.
+		QColor col (in.chars ());
+		if (col.isValid()) {
+			r = col.red ();
+			g = col.green ();
+			b = col.blue ();
+			return true;
+		}
+#else
+		if (in[0] == '#') {
+			// Hex code
+			
+			if (~in != 4 && ~in != 7)
+				return false; // bad length
+			
+			printf ("%s\n", in.chars ());
+			
+			if (~in == 4) {
+				in.format ("#%c%c%c%c%c%c",
+					in[1], in[1],
+					in[2], in[2],
+					in[3], in[3]);
+			}
+			
+			printf ("%s\n", in.chars ());
+			
+			if (sscanf (in.chars(), "#%2hhx%2hhx%2hhx", &r, &g, &b))
+				return true;
+		}
+#endif // QT_VERSION
+		
+		return false;
+	}
+	
+	str toString () {
+		str val;
+		val.format ("#%.2X%.2X%.2X", r, g, b);
+		return val;
+	}
+	
+	char* chars() {
+		return toString ().chars ();
+	}
+	
+	operator str () {
+		return toString ();
+	}
+	
+	void operator= (const str other) {
+		parseFromString (other);
+	}
+};
+
+#endif // __COLOR_H__
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,121 @@
+#ifndef __COMMON_H__
+#define __COMMON_H__
+
+#define APPNAME "ldforge"
+#define APPNAME_DISPLAY "LDForge"
+#define APPNAME_CAPS "LDFORGE"
+
+#define VERSION_MAJOR 0
+#define VERSION_MAJOR_STR "0"
+#define VERSION_MINOR 1
+#define VERSION_MINOR_STR "1"
+
+#define VERSION_STRING VERSION_MAJOR_STR "." VERSION_MINOR_STR
+
+#define CONFIG_WITH_QT
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <vector>
+#include <stdint.h>
+#include "stdarg.h"
+#include "str.h"
+#include "config.h"
+
+using std::vector;
+
+class LDForgeWindow;
+class LDObject;
+class bbox;
+
+// =============================================================================
+// vertex (v)
+// 
+// Vertex class. Not to be confused with LDVertex, which is a vertex used in an
+// LDraw code file.
+//
+// Methods:
+// - midpoint (vertex&): returns a midpoint
+// =============================================================================
+class vertex {
+public:
+	double x, y, z;
+	
+	// =========================================================================
+	// Midpoint between this vertex and another vertex.
+	vertex midpoint (vertex& other);
+	str getStringRep ();
+};
+
+// =============================================================================
+// bearing
+// 
+// A bearing is a combination of an angle and a pitch. Essentially a 3D angle.
+// The project method projects a vertex from a given vertex by a given length.
+// 
+// Prefix: g, since b is bool
+// =============================================================================
+class bearing {
+	double fAngle, fPitch;
+	
+	vertex project (vertex& vSource, ulong ulLength);
+};
+
+// =============================================================================
+typedef struct {
+	str filename;
+	vector<LDObject*> objects;
+} OpenFile;
+
+// 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
+
+typedef unsigned char byte;
+
+template<class T> inline T clamp (T a, T min, T max) {
+	return (a > max) ? max : (a < min) ? min : a;
+}
+
+template<class T> inline T min (T a, T b) {
+	return (a < b) ? a : b;
+}
+
+template<class T> inline T max (T a, T b) {
+	return (a > b) ? a : b;
+}
+
+static const double pi = 3.14159265358979323846f;
+
+// main.cpp
+extern OpenFile* g_CurrentFile;
+extern bbox g_BBox;
+extern LDForgeWindow* g_qWindow;
+extern vector<OpenFile*> g_LoadedFiles;
+
+#ifndef unix
+typedef unsigned int uint;
+typedef unsigned long ulong;
+#endif // unix
+
+typedef int8_t xchar;
+typedef int16_t xshort;
+typedef int32_t xlong;
+typedef int64_t xlonglong;
+typedef uint8_t xuchar;
+typedef uint16_t xushort;
+typedef uint32_t xulong;
+typedef uint64_t xulonglong;
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/config.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,351 @@
+#include <stdio.h>
+// #include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include "str.h"
+#include "config.h"
+
+#ifdef CONFIG_WITH_QT
+#include <QDir>
+#endif // CONFIG_WITH_QT
+
+// =============================================================================
+// Define the configs
+#define CFG(TYPE, SECT, NAME, DESCR, DEFAULT) \
+	TYPE##config SECT##_##NAME (CFGSECTNAME (SECT), DESCR, \
+		DEFAULT, #NAME, #SECT "_" #NAME, #TYPE, #DEFAULT);
+
+#define SECT(...)
+ #include "cfgdef.h"
+#undef CFG
+#undef SECT
+
+// =============================================================================
+config* config::pointers[] = {
+#define CFG(TYPE, SECT, NAME, DESCR, DEFAULT) &SECT##_##NAME,
+#define SECT(...)
+ #include "cfgdef.h"
+#undef CFG
+#undef SECT
+};
+
+// =============================================================================
+const char* config::sections[] = {
+#define CFG(...)
+#define SECT(A,B) #A,
+ #include "cfgdef.h"
+#undef CFG
+#undef SECT
+};
+
+// =============================================================================
+const char* config::sectionNames[] = {
+#define CFG(...)
+#define SECT(A,B) #B,
+ #include "cfgdef.h"
+#undef CFG
+#undef SECT
+};
+
+// =============================================================================
+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",
+	"Color",
+};
+
+// =============================================================================
+// Load the configuration from file
+bool config::load () {
+	FILE* fp = fopen (filepath().chars(), "r");
+	char linedata[MAX_INI_LINE];
+	char* line;
+	size_t ln = 0;
+	configsection_e section = NO_CONFIG_SECTION;
+	
+	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.
+		
+		if (line[0] == '[') {
+			// Section
+			char* endbracket = strchr (line, ']');
+			
+			if (!endbracket) {
+				fprintf (stderr, "badly formed section: %s", line);
+				continue;
+			}
+			
+			str sectionName = str (line).substr (1, endbracket - line);
+			const configsection_e oldsection = section;
+			section = NO_CONFIG_SECTION;
+			
+			// Find the section
+			for (unsigned i = 0; i < NUM_ConfigSections && section == NO_CONFIG_SECTION; i++)
+				if (sectionName.compare (sectionNames[i]) == 0)
+					section = (configsection_e)i;
+			
+			if (section == NO_CONFIG_SECTION) {
+				fprintf (stderr, "unknown config section `%s`\n", sectionName.chars());
+				section = oldsection;
+			}
+			
+			continue;
+		}
+		
+		// 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);
+		
+		str configname;
+		configname.format ("%s_%s", sections[section], entry.chars ());
+		
+		// Find the config entry for this.
+		config* cfg = NULL;
+		for (size_t i = 0; i < NUM_CONFIG && !cfg; i++)
+			if (configname.compare (pointers[i]->fullname) == 0)
+				cfg = pointers[i];
+		
+		if (!cfg) {
+			fprintf (stderr, "unknown config `%s`\n", configname.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 (const_cast<config*> (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_color:
+			static_cast<colorconfig*> (cfg)->value.parseFromString (valstring);
+			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 () {
+#ifdef APPNAME
+	#ifdef CONFIG_WITH_QT
+		// If the directory doesn't exist, create it now.
+		if (!QDir (dirpath().chars()).exists ()) {
+			fprintf (stderr, "Creating config path %s...\n", dirpath().chars());
+			if (!QDir ().mkpath (dirpath().chars())) {
+				fprintf (stderr, "Failed to create the directory. Configuration cannot be saved!\n");
+				return false; // Couldn't create directory
+			}
+		}
+	#else
+		#warning Need QT to check for directories. Config will not be able
+		#warning to save properly if ~/.APPNAME/ does not exist.
+	#endif // CONFIG_WITH_QT
+#endif // CONFIG_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);
+	writef (fp, "\n");
+	
+	for (int i = 0; i < NUM_ConfigSections; i++) {
+		if (i > 0)
+			writef (fp, "\n");
+		
+		writef (fp, "[%s]\n", sectionNames[i]);
+		bool first = true;
+		
+		for (size_t j = 0; j < NUM_CONFIG; j++) {
+			config* cfg = pointers[j];
+			
+			if (cfg->sect != i)
+				continue;
+			
+			if (!first)
+				writef (fp, "\n");
+			
+			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_color:
+				valstring = (str)(static_cast<colorconfig*> (cfg)->value);
+				break;
+			default:
+				break;
+			}
+			
+			// Write the entry now.
+			writef (fp, "# %s, default: %s\n", g_ConfigTypeNames[cfg->getType()], cfg->defaultstring);
+			writef (fp, "# %s: %s\n", cfg->fullname, cfg->description);
+			writef (fp, "%s=%s\n", cfg->name, valstring.chars());
+			first = false;
+		}
+	}
+	
+	fclose (fp);
+	return true;
+}
+
+// =============================================================================
+void config::reset () {
+	for (size_t i = 0; i < NUM_CONFIG; i++)
+		pointers[i]->resetValue ();
+}
+
+// =============================================================================
+str config::filepath () {
+#ifdef APPNAME
+	str path;
+	path.format ("%s" APPNAME ".ini", dirpath().chars());
+	return path;
+#else // APPNAME
+	return "config.ini";
+#endif // APPNAME
+}
+
+// =============================================================================
+str config::dirpath () {
+#ifdef APPNAME
+	str path;
+	
+	#ifdef CONFIG_WITH_QT
+		path = (QDir::homePath ().toStdString().c_str());
+	#else // CONFIG_WITH_QT
+		path = "~";
+	#endif // CONFIG_WITH_QT
+	
+	path += "/." APPNAME "/";
+#else
+	path = "./";
+#endif // APPNAME
+	
+	return path;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/config.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,238 @@
+#ifndef __OPTIONS_H__
+#define __OPTIONS_H__
+
+#include "common.h"
+#include "str.h"
+#include "color.h"
+
+// =============================================================================
+// Determine configuration file. Use APPNAME if given.
+#ifdef APPNAME
+ #define CONFIGFILE APPNAME ".ini"
+#else // APPNAME
+ #define APPNAME "(unnamed application)"
+ #define CONFIGFILE "config.ini"
+#endif // APPNAME
+
+#ifdef CONFIG_WITH_QT
+ #include <QString>
+#endif // CONFIG_WITH_QT
+
+// -------------------------------
+#define CFGSECTNAME(X) CFGSECT_##X
+
+#define MAX_INI_LINE 512
+#define NUM_CONFIG (sizeof config::pointers / sizeof *config::pointers)
+
+// =============================================================================
+enum configsection_e {
+#define CFG(...)
+#define SECT(A,B) CFGSECTNAME (A),
+ #include "cfgdef.h"
+#undef CFG
+#undef SECT
+	NUM_ConfigSections,
+	NO_CONFIG_SECTION = -1
+};
+
+// =============================================================================
+enum configtype_e {
+	CONFIG_none,
+	CONFIG_int,
+	CONFIG_str,
+	CONFIG_float,
+	CONFIG_bool,
+	CONFIG_color,
+};
+
+// =========================================================
+class config {
+public:
+	configsection_e sect;
+	const char* description, *name, *fullname, *typestring, *defaultstring;
+	
+	virtual configtype_e getType () {
+		return CONFIG_none;
+	}
+	
+	virtual void resetValue () {}
+	
+	// ------------------------------------------
+	static bool load ();
+	static bool save ();
+	static void reset ();
+	static config* pointers[];
+	static const char* sections[];
+	static const char* sectionNames[];
+	static str dirpath ();
+	static str filepath ();
+};
+
+// =============================================================================
+#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 (const configsection_e _sect, const char* _description, \
+		T _defval, const char* _name, const char* _fullname, const char* _typestring, \
+		const char* _defaultstring) \
+	{ \
+		sect = _sect; \
+		description = _description; \
+		value = defval = _defval; \
+		name = _name; \
+		fullname = _fullname; \
+		typestring = _typestring; \
+		defaultstring = _defaultstring; \
+	} \
+	operator T () { \
+		return value; \
+	} \
+	configtype_e getType () { \
+		return CONFIG_##T; \
+	} \
+	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, =)
+};
+
+// =============================================================================
+CONFIGTYPE (color) {
+public:
+	IMPLEMENT_CONFIG (color)
+	// DEFINE_COMPARE_OPERATOR (color, ==)
+	// DEFINE_COMPARE_OPERATOR (color, !=)
+};
+
+// =============================================================================
+// Extern the configurations now
+#define CFG(TYPE, SECT, NAME, DESCR, DEFAULT) extern TYPE##config SECT##_##NAME;
+#define SECT(...)
+ #include "cfgdef.h"
+#undef CFG
+#undef SECT
+
+#endif // __OPTIONS_H__
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/draw.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,159 @@
+#include <QtGui>
+#include <QGLWidget>
+#include "common.h"
+#include "io.h"
+#include "draw.h"
+#include "bbox.h"
+
+renderer::renderer (QWidget* parent) {
+	parent = parent; // shhh, GCC
+	fRotX = fRotY = fRotZ = 0.0;
+	fZoom = 1.0;
+}
+
+void renderer::initializeGL () {
+	glLoadIdentity();
+	glMatrixMode (GL_MODELVIEW);
+	glClearColor (0.8f, 0.8f, 0.85f, 1.0f);
+	glEnable (GL_DEPTH_TEST);
+	glShadeModel (GL_SMOOTH);
+	glEnable (GL_MULTISAMPLE);
+	
+	CompileObjects ();
+}
+
+void renderer::hardRefresh () {
+	CompileObjects ();
+	paintGL ();
+}
+
+void renderer::resizeGL (int w, int h) {
+	glViewport (0, 0, w, h);
+}
+
+void renderer::paintGL () {
+	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	
+	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	glPushMatrix ();
+		glTranslatef (
+			(g_BBox.v0.x + g_BBox.v1.x) / -2.0,
+			(g_BBox.v0.y + g_BBox.v1.y) / -2.0,
+			(g_BBox.v0.z + g_BBox.v1.z) / -2.0
+		);
+		
+		// glTranslatef (0.0f, 0.0f, -5.0f);
+		glTranslatef (0.0f, 0.0f, fZoom);
+		
+		// glScalef (0.75f, 1.15f, 0.0f);
+		glRotatef (fRotX, 1.0f, 0.0f, 0.0f);
+		glRotatef (fRotY, 0.0f, 1.0f, 0.0f);
+		glRotatef (fRotZ, 0.0f, 0.0f, 1.0f);
+		
+		glMatrixMode (GL_MODELVIEW);
+		
+		glCallList (objlist);
+		glColor3f (0.0, 0.5, 1.0);
+	glPopMatrix ();
+}
+
+void renderer::CompileObjects () {
+	printf ("compile all objects\n");
+	
+	objlist = glGenLists (1);
+	glNewList (objlist, GL_COMPILE);
+	
+	if (!g_CurrentFile) {
+		printf ("renderer: no files loaded, cannot compile anything\n");
+		return;
+	}
+	
+	for (ulong i = 0; i < g_CurrentFile->objects.size(); i++)
+		CompileOneObject (g_CurrentFile->objects[i]);
+	
+	glEndList ();
+}
+
+#define GL_VERTEX(V) \
+	glVertex3d (V.x, V.y, V.z);
+
+void renderer::CompileOneObject (LDObject* obj) {
+	if (!obj)
+		return;
+	
+	switch (obj->getType ()) {
+	case OBJ_CondLine: // For now, treat condlines like normal lines.
+	case OBJ_Line:
+		{
+			glColor3f (0.0f, 0.0f, 0.0f); // Draw all lines black for now
+			// draw lines
+			LDLine* line = static_cast<LDLine*> (obj);
+			glBegin (GL_LINES);
+			for (short i = 0; i < 2; ++i)
+				GL_VERTEX (line->vaCoords[i])
+			glEnd ();
+		}
+		break;
+	
+	case OBJ_Triangle:
+		{
+			LDTriangle* tri = static_cast<LDTriangle*> (obj);
+			glColor3f (0.5f, 0.5f, 0.5f); // Draw all polygons gray for now
+			glBegin (GL_TRIANGLES);
+			for (short i = 0; i < 3; ++i)
+				GL_VERTEX (tri->vaCoords[i])
+			glEnd ();
+		}
+		break;
+		
+	case OBJ_Quad:
+		{
+			LDQuad* quad = static_cast<LDQuad*> (obj);
+			glColor3f (0.5f, 0.5f, 0.5f);
+			glBegin (GL_QUADS);
+			for (short i = 0; i < 4; ++i)
+				GL_VERTEX (quad->vaCoords[i])
+			glEnd ();
+		}
+		break;
+	
+	default:
+		break;
+	}
+}
+
+void renderer::ClampAngle (double& fAngle) {
+	while (fAngle < 0)
+		fAngle += 360.0;
+	while (fAngle > 360.0)
+		fAngle -= 360.0;
+}
+
+void renderer::mouseMoveEvent (QMouseEvent *event) {
+	int dx = event->x () - lastPos.x ();
+	int dy = event->y () - lastPos.y ();
+	
+	if (event->buttons () & Qt::LeftButton) {
+		fRotX = fRotX + (dy);
+		fRotY = fRotY + (dx);
+		ClampAngle (fRotX);
+		ClampAngle (fRotY);
+	}
+	
+	if (event->buttons () & Qt::RightButton) {
+		fRotX = fRotX + (dy);
+		fRotZ = fRotZ + (dx);
+		ClampAngle (fRotX);
+		ClampAngle (fRotZ);
+	}
+	
+	if (event->buttons () & Qt::MidButton) {
+		fZoom += (dy / 100.0);
+		fZoom = clamp (fZoom, 0.1, 10.0);
+	}
+	
+	printf ("%.3f %.3f %.3f %.3f\n",
+		fRotX, fRotY, fRotZ, fZoom);
+	lastPos = event->pos();
+	updateGL ();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/draw.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,32 @@
+#ifndef __REND_H__
+#define __REND_H__
+
+#include <QGLWidget>
+#include "common.h"
+#include "ldtypes.h"
+
+class renderer : public QGLWidget {
+	Q_OBJECT
+	
+public:
+	renderer(QWidget* parent = NULL);
+	void hardRefresh ();
+	void CompileObjects ();
+	double fRotX, fRotY, fRotZ;
+	QPoint lastPos;
+	double fZoom;
+
+protected:
+	void initializeGL ();
+	void resizeGL (int w, int h);
+	void paintGL ();
+	
+	void mouseMoveEvent (QMouseEvent *event);
+
+private:
+	GLuint objlist;
+	void CompileOneObject (LDObject* obj);
+	void ClampAngle (double& fAngle);
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,353 @@
+#include <QtGui>
+
+#include "common.h"
+#include "draw.h"
+#include "gui.h"
+#include "model.h"
+#include "io.h"
+
+LDForgeWindow::LDForgeWindow () {
+	R = new renderer;
+	
+	qObjList = new QTreeWidget;
+	qObjList->setHeaderHidden (true);
+	qObjList->setMaximumWidth (256);
+	
+	qMessageLog = new QTextEdit;
+	qMessageLog->setReadOnly (true);
+	qMessageLog->setMaximumHeight (96);
+	
+	QWidget* w = new QWidget;
+	QGridLayout* layout = new QGridLayout;
+	layout->setColumnMinimumWidth (0, 192);
+	layout->setColumnStretch (0, 1);
+	layout->addWidget (R,			0, 0);
+	layout->addWidget (qObjList,	0, 1);
+	layout->addWidget (qMessageLog,	1, 0, 1, 2);
+	w->setLayout (layout);
+	setCentralWidget (w);
+	
+	createMenuActions ();
+	createMenus ();
+	createToolbars ();
+	
+	setWindowTitle (APPNAME_DISPLAY " v" VERSION_STRING);
+	setMinimumSize (320, 200);
+	resize (800, 600);
+}
+
+#define MAKE_ACTION(OBJECT, DISPLAYNAME, IMAGENAME, DESCR) \
+	qAct_##OBJECT = new QAction (QIcon ("./icons/" IMAGENAME ".png"), tr (DISPLAYNAME), this); \
+	qAct_##OBJECT->setStatusTip (tr (DESCR)); \
+	connect (qAct_##OBJECT, SIGNAL (triggered ()), this, SLOT (slot_##OBJECT ()));
+
+void LDForgeWindow::createMenuActions () {
+	// Long menu names go here so my cool action definition table doesn't get out of proportions
+	const char* sNewCdLineText = "New Conditional Line",
+		*sNewQuadText = "New Quadrilateral",
+		*sAboutText = "About " APPNAME_DISPLAY;
+	
+	MAKE_ACTION (new,			"&New",			"file-new",		"Create a new part model.")
+	MAKE_ACTION (open,			"&Open",		"open",			"Load a part model from a file.")
+	MAKE_ACTION (save,			"&Save",		"save",			"Save the part model.")
+	MAKE_ACTION (saveAs,		"Save &As",		"save-as",		"Save the part to a specific file.")
+	MAKE_ACTION (exit,			"&Exit",		"exit",			"Close " APPNAME_DISPLAY ".")
+	
+	MAKE_ACTION (cut,			"Cut",			"cut",			"Cut the current selection to clipboard.")
+	MAKE_ACTION (copy,			"Copy",			"copy",			"Copy the current selection to clipboard.")
+	MAKE_ACTION (paste,			"Paste",		"paste",		"Paste clipboard contents.")
+	MAKE_ACTION (about,			sAboutText,		"about",		"Shows information about " APPNAME_DISPLAY ".")
+	MAKE_ACTION (aboutQt,		"About Qt",		"aboutQt",		"Shows information about Qt.")
+	
+	MAKE_ACTION (newSubfile,	"New Subfile",	"add-subfile",	"Creates a new subfile reference.")
+	MAKE_ACTION (newLine,		"New Line", 	"add-line",		"Creates a new line.")
+	MAKE_ACTION (newTriangle,	"New Triangle", "add-triangle",	"Creates a new triangle.")
+	MAKE_ACTION (newQuad,		sNewQuadText,	"add-quad",		"Creates a new quadrilateral.")
+	MAKE_ACTION (newCondLine,	sNewCdLineText,	"add-condline",	"Creates a new conditional line.");
+	MAKE_ACTION (newComment,	"New Comment",	"add-comment",	"Creates a new comment.");
+	MAKE_ACTION (newVector,		"New Vector",	"add-vector",	"Creates a new vector.")
+	MAKE_ACTION (newVertex,		"New Vertex",	"add-vertex",	"Creates a new vertex.")
+	
+	// Keyboard shortcuts
+	qAct_new->setShortcut (Qt::CTRL | Qt::Key_N);
+	qAct_open->setShortcut (Qt::CTRL | Qt::Key_O);
+	qAct_save->setShortcut (Qt::CTRL | Qt::Key_S);
+	qAct_saveAs->setShortcut (Qt::CTRL | Qt::SHIFT | Qt::Key_S);
+	
+	qAct_cut->setShortcut (Qt::CTRL | Qt::Key_X);
+	qAct_copy->setShortcut (Qt::CTRL | Qt::Key_C);
+	qAct_paste->setShortcut (Qt::CTRL | Qt::Key_V);
+	
+	// things not implemented yet
+	QAction* qaDisabledActions[] = {
+		qAct_save,
+		qAct_saveAs,
+		qAct_newSubfile,
+		qAct_newLine,
+		qAct_newTriangle,
+		qAct_newQuad,
+		qAct_newCondLine,
+		qAct_newComment,
+		qAct_newVector,
+		qAct_newVertex,
+		qAct_cut,
+		qAct_copy,
+		qAct_paste,
+		qAct_about
+	};
+	
+	for (ushort i = 0; i < sizeof qaDisabledActions / sizeof *qaDisabledActions; ++i)
+		qaDisabledActions[i]->setEnabled (false);
+}
+
+void LDForgeWindow::createMenus () {
+	// File menu
+	qFileMenu = menuBar ()->addMenu (tr ("&File"));
+	qFileMenu->addAction (qAct_new);			// New
+	qFileMenu->addAction (qAct_open);			// Open
+	qFileMenu->addAction (qAct_save);			// Save
+	qFileMenu->addAction (qAct_saveAs);			// Save As
+	qFileMenu->addSeparator ();					// -------
+	qFileMenu->addAction (qAct_exit);			// Exit
+	
+	// Edit menu
+	qInsertMenu = menuBar ()->addMenu (tr ("&Insert"));
+	qInsertMenu->addAction (qAct_newSubfile);	// New Subfile
+	qInsertMenu->addAction (qAct_newLine);		// New Line
+	qInsertMenu->addAction (qAct_newTriangle);	// New Triangle
+	qInsertMenu->addAction (qAct_newQuad);		// New Quad
+	qInsertMenu->addAction (qAct_newCondLine);	// New Conditional Line
+	qInsertMenu->addAction (qAct_newComment);	// New Comment
+	qInsertMenu->addAction (qAct_newVector);	// New Vector
+	qInsertMenu->addAction (qAct_newVertex);	// New Vertex
+	
+	qEditMenu = menuBar ()->addMenu (tr ("&Edit"));
+	qEditMenu->addAction (qAct_cut);			// Cut
+	qEditMenu->addAction (qAct_copy);			// Copy
+	qEditMenu->addAction (qAct_paste);			// Paste
+	
+	// Help menu
+	qHelpMenu = menuBar ()->addMenu (tr ("&Help"));
+	qHelpMenu->addAction (qAct_about);			// About
+	qHelpMenu->addAction (qAct_aboutQt);		// About Qt
+}
+
+void LDForgeWindow::createToolbars () {
+	qFileToolBar = new QToolBar ("File");
+	qFileToolBar->addAction (qAct_new);
+	qFileToolBar->addAction (qAct_open);
+	qFileToolBar->addAction (qAct_save);
+	qFileToolBar->addAction (qAct_saveAs);
+	addToolBar (qFileToolBar);
+	
+	qInsertToolBar = new QToolBar ("Insert");
+	qInsertToolBar->addAction (qAct_newSubfile);
+	qInsertToolBar->addAction (qAct_newLine);
+	qInsertToolBar->addAction (qAct_newTriangle);
+	qInsertToolBar->addAction (qAct_newQuad);
+	qInsertToolBar->addAction (qAct_newCondLine);
+	qInsertToolBar->addAction (qAct_newComment);
+	qInsertToolBar->addAction (qAct_newVector);
+	qInsertToolBar->addAction (qAct_newVertex);
+	addToolBar (qInsertToolBar);
+	
+	qEditToolBar = new QToolBar ("Edit");
+	qEditToolBar->addAction (qAct_cut);
+	qEditToolBar->addAction (qAct_copy);
+	qEditToolBar->addAction (qAct_paste);
+	addToolBar (qEditToolBar);
+}
+
+void LDForgeWindow::slot_new () {
+	printf ("new file\n");
+	
+	closeModel ();
+	newModel ();
+}
+
+void LDForgeWindow::slot_open () {
+	str name = QFileDialog::getOpenFileName (this, "Open file",
+		"", "LDraw files (*.dat *.ldr)").toStdString().c_str();
+	
+	if (g_LoadedFiles.size())
+		closeModel ();
+	
+	IO_ParseLDFile (name);
+}
+
+void LDForgeWindow::slot_save () {
+	printf ("save file\n");
+}
+
+void LDForgeWindow::slot_saveAs () {
+	printf ("save as file\n");
+}
+
+void LDForgeWindow::slot_exit () {
+	printf ("exit\n");
+	exit (0);
+}
+
+void LDForgeWindow::slot_newSubfile () {
+	
+}
+
+void LDForgeWindow::slot_newLine () {
+	
+}
+
+void LDForgeWindow::slot_newTriangle () {
+	
+}
+
+void LDForgeWindow::slot_newQuad () {
+	
+}
+
+void LDForgeWindow::slot_newCondLine () {
+
+}
+
+void LDForgeWindow::slot_newComment () {
+
+}
+
+void LDForgeWindow::slot_about () {
+	
+}
+
+void LDForgeWindow::slot_aboutQt () {
+	QMessageBox::aboutQt (this);
+}
+
+void LDForgeWindow::slot_cut () {
+
+}
+
+void LDForgeWindow::slot_copy () {
+
+}
+
+void LDForgeWindow::slot_paste () {
+	
+}
+
+void LDForgeWindow::slot_newVector () {
+	
+}
+
+void LDForgeWindow::slot_newVertex () {
+	
+}
+
+
+static QIcon IconForObjectType (LDObject* obj) {
+	switch (obj->getType ()) {
+	case OBJ_Empty:
+		return QIcon ("icons/empty.png");
+	
+	case OBJ_Line:
+		return QIcon ("icons/line.png");
+	
+	case OBJ_Quad:
+		return QIcon ("icons/quad.png");
+	
+	case OBJ_Subfile:
+		return QIcon ("icons/subfile.png");
+	
+	case OBJ_Triangle:
+		return QIcon ("icons/triangle.png");
+	
+	case OBJ_CondLine:
+		return QIcon ("icons/condline.png");
+	
+	case OBJ_Comment:
+		return QIcon ("icons/comment.png");
+	
+	case OBJ_Vector:
+		return QIcon ("icons/vector.png");
+	
+	case OBJ_Vertex:
+		return QIcon ("icons/vertex.png");
+	
+	case OBJ_Gibberish:
+	case OBJ_Unidentified:
+		return QIcon ("icons/error.png");
+	}
+	
+	return QIcon ();
+}
+
+void LDForgeWindow::buildObjList () {
+	QList<QTreeWidgetItem*> qaItems;
+	for (ushort i = 0; i < g_CurrentFile->objects.size(); ++i) {
+		LDObject* obj = g_CurrentFile->objects[i];
+		
+		str zText;
+		switch (obj->getType ()) {
+		case OBJ_Comment:
+			zText = static_cast<LDComment*> (obj)->zText;
+			break;
+		
+		case OBJ_Empty:
+			break; // leave it empty
+		
+		case OBJ_Line:
+		case OBJ_CondLine:
+			{
+				LDLine* line = static_cast<LDLine*> (obj);
+				zText.format ("%s, %s",
+					line->vaCoords[0].getStringRep ().chars(),
+					line->vaCoords[1].getStringRep ().chars());
+			}
+			break;
+		
+		case OBJ_Triangle:
+			{
+				LDTriangle* triangle = static_cast<LDTriangle*> (obj);
+				zText.format ("%s, %s, %s",
+					triangle->vaCoords[0].getStringRep ().chars(),
+					triangle->vaCoords[1].getStringRep ().chars(),
+					triangle->vaCoords[2].getStringRep ().chars());
+			}
+			break;
+		
+		case OBJ_Quad:
+			{
+				LDQuad* quad = static_cast<LDQuad*> (obj);
+				zText.format ("%s, %s, %s",
+					quad->vaCoords[0].getStringRep ().chars(),
+					quad->vaCoords[1].getStringRep ().chars(),
+					quad->vaCoords[2].getStringRep ().chars(),
+					quad->vaCoords[3].getStringRep ().chars());
+			}
+			break;
+		
+		case OBJ_Gibberish:
+			zText.format ("ERROR: %s",
+				static_cast<LDGibberish*> (obj)->zContent.chars());
+			break;
+		
+		case OBJ_Vector:
+			zText.format ("%s", static_cast<LDVector*> (obj)->vPos.getStringRep().chars());
+			break;
+		
+		case OBJ_Vertex:
+			zText.format ("%s", static_cast<LDVertex*> (obj)->vPosition.getStringRep().chars());
+			break;
+		
+		default:
+			zText = g_saObjTypeNames[obj->getType ()];
+			break;
+		}
+		
+		QTreeWidgetItem* item = new QTreeWidgetItem ((QTreeWidget*)nullptr,
+			QStringList (zText.chars()), 0);
+		item->setIcon (0, IconForObjectType (obj));
+		
+		qaItems.append (item);
+	}
+	
+	printf ("insert top level items\n");
+	qObjList->insertTopLevelItems (0, qaItems);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,76 @@
+#ifndef __GUI_H__
+#define __GUI_H__
+
+#include <QMainWindow>
+#include <QMenu>
+#include <QToolBar>
+#include <QAction>
+#include <QTreeWidget>
+#include <QToolBar>
+#include <QTextEdit>
+#include "draw.h"
+
+class LDForgeWindow : public QMainWindow {
+	Q_OBJECT
+	
+public:
+	renderer* R;
+	
+	// Object list view
+	QTreeWidget* qObjList;
+	
+	// Message log
+	QTextEdit* qMessageLog;
+	
+	// Menus
+	QMenu* qFileMenu, *qEditMenu, *qInsertMenu, *qHelpMenu;
+	
+	// Toolbars
+	QToolBar* qFileToolBar, *qEditToolBar, *qInsertToolBar;
+	
+	// ACTION ARMADA
+	QAction* qAct_new, *qAct_open, *qAct_save, *qAct_saveAs, *qAct_exit;
+	QAction* qAct_cut, *qAct_copy, *qAct_paste;
+	QAction* qAct_newSubfile, *qAct_newLine, *qAct_newTriangle, *qAct_newQuad;
+	QAction* qAct_newCondLine, *qAct_newComment, *qAct_newVector, *qAct_newVertex;
+	QAction* qAct_about, *qAct_aboutQt;
+	
+	LDForgeWindow ();
+	void buildObjList ();
+	
+private:
+	void createMenuActions ();
+	void createMenus ();
+    void createToolbars ();
+
+private slots:
+	void slot_new ();
+	void slot_open ();
+	void slot_save ();
+	void slot_saveAs ();
+	void slot_exit ();
+	
+	void slot_newSubfile ();
+	void slot_newLine ();
+	void slot_newTriangle ();
+	void slot_newQuad ();
+	void slot_newCondLine ();
+	void slot_newComment ();
+	void slot_newVector ();
+	void slot_newVertex ();
+	
+	void slot_cut ();
+	void slot_copy ();
+	void slot_paste ();
+	
+	void slot_about ();
+	void slot_aboutQt ();
+};
+
+enum {
+	LDOLC_Icon,
+	LDOLC_Data,
+	NUM_LDOL_Columns
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/icons/.directory	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,4 @@
+[Dolphin]
+PreviewsShown=true
+Timestamp=2013,3,14,16,3,19
+Version=3
Binary file icons/add-line.png has changed
Binary file icons/add-quad.png has changed
Binary file icons/add-subfile.png has changed
Binary file icons/add-triangle.png has changed
Binary file icons/add-vector.png has changed
Binary file icons/copy.png has changed
Binary file icons/cut.png has changed
Binary file icons/del-line.png has changed
Binary file icons/empty.png has changed
Binary file icons/error.png has changed
Binary file icons/file-new.png has changed
Binary file icons/file.png has changed
Binary file icons/line.png has changed
Binary file icons/paste.png has changed
Binary file icons/quad.png has changed
Binary file icons/save-as.png has changed
Binary file icons/save.png has changed
Binary file icons/subfile.png has changed
Binary file icons/triangle.png has changed
Binary file icons/vector.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/io.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,161 @@
+#include <vector>
+
+#include "common.h"
+#include "io.h"
+#include "ldtypes.h"
+#include "misc.h"
+
+// =============================================================================
+// IO_FindLoadedFile (str)
+//
+// Returns a pointer to the first found open file with the given name.
+// =============================================================================
+OpenFile* IO_FindLoadedFile (str name) {
+	OpenFile* file;
+	
+	for (uint i = 0; i < g_LoadedFiles.size(); i++) {
+		file = g_LoadedFiles[i];
+		if (!file->filename.icompare (name))
+			return file;
+	}
+	
+	return NULL;
+}
+
+// =============================================================================
+// IO_ParseLDFile (str)
+//
+// Opens the given file and parses the LDraw code within.
+// =============================================================================
+OpenFile* IO_ParseLDFile (str path) {
+	FILE* fp = fopen (path.chars (), "r");
+	
+	if (!fp) {
+		printf ("Couldn't open %s!\n", path.chars ());
+		return NULL;
+	}
+	
+	OpenFile* load = new OpenFile;
+	load->filename = path;
+	
+	vector<str> lines;
+	
+	{
+		char line[1024];
+		while (fgets (line, sizeof line, fp)) {
+			// Trim the trailing newline
+			str zLine = line;
+			while (zLine[~zLine - 1] == '\n' || zLine[~zLine - 1] == '\r')
+				zLine -= 1;
+			
+			lines.push_back (zLine);
+		}
+	}
+	
+	fclose (fp);
+	
+	for (ulong i = 0; i < lines.size(); ++i)
+		load->objects.push_back (ParseLine (lines[i]));
+	
+	g_LoadedFiles.push_back (load);
+	return g_LoadedFiles[g_LoadedFiles.size() - 1];
+}
+
+// =============================================================================
+// ParseLine (str)
+//
+// Parses a string line containing an LDraw object and returns the object parsed.
+// =============================================================================
+LDObject* ParseLine (str zLine) {
+	str zNoWhitespace = zLine;
+	StripWhitespace (zNoWhitespace);
+	if (!~zNoWhitespace) {
+		// Line was empty, or only consisted of whitespace
+		return new LDEmpty;
+	}
+	
+	char c = zLine[0];
+	vector<str> tokens = zLine / " ";
+	printf ("line: %s\n", zLine.chars());
+	
+	switch (c - '0') {
+	case 0:
+		{
+			// Comment
+			LDComment* obj = new LDComment;
+			obj->zText = zLine.substr (1, -1);
+			printf ("\t-> comment (%s)\n", obj->zText.chars());
+			return obj;
+		}
+	
+	case 1:
+		{
+			// Subfile
+			LDSubfile* obj = new LDSubfile;
+			obj->dColor = atoi (tokens[1]);
+			obj->vPosition = ParseVertex (zLine, 2); // 2 - 4
+			
+			for (short i = 0; i < 9; ++i)
+				obj->faMatrix[i] = atof (tokens[i + 5]); // 5 - 13
+			
+			obj->zFileName = tokens[14];
+			return obj;
+		}
+	
+	case 2:
+		{
+			// Line
+			LDLine* obj = new LDLine;
+			obj->dColor = GetWordInt (zLine, 1);
+			for (short i = 0; i < 2; ++i)
+				obj->vaCoords[i] = ParseVertex (zLine, 2 + (i * 3)); // 2 - 7
+			return obj;
+		}
+	
+	case 3:
+		{
+			// Triangle
+			LDTriangle* obj = new LDTriangle;
+			obj->dColor = GetWordInt (zLine, 1);
+			
+			for (short i = 0; i < 3; ++i)
+				obj->vaCoords[i] = ParseVertex (zLine, 2 + (i * 3)); // 2 - 10
+			
+			return obj;
+		}
+	
+	case 4:
+		{
+			// Quadrilateral
+			LDQuad* obj = new LDQuad;
+			obj->dColor = GetWordInt (zLine, 1);
+			
+			for (short i = 0; i < 4; ++i)
+				obj->vaCoords[i] = ParseVertex (zLine, 2 + (i * 3)); // 2 - 13
+			
+			return obj;
+		}
+	
+	case 5:
+		{
+			// Conditional line
+			LDCondLine* obj = new LDCondLine;
+			obj->dColor = GetWordInt (zLine, 1);
+			
+			for (short i = 0; i < 2; ++i)
+				obj->vaCoords[i] = ParseVertex (zLine, 2 + (i * 3)); // 2 - 7
+			
+			for (short i = 0; i < 2; ++i)
+				obj->vaControl[i] = ParseVertex (zLine, 8 + (i * 3)); // 8 - 13
+			return obj;
+		}
+		
+	default:
+		{
+			// Strange line we couldn't parse
+			LDGibberish* obj = new LDGibberish;
+			obj->zContent = zLine;
+			return obj;
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/io.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,15 @@
+#ifndef __IO_H__
+#define __IO_H__
+
+#include "common.h"
+#include "ldtypes.h"
+#include "str.h"
+
+// PROTOTYPES
+OpenFile* IO_FindLoadedFile (str name);
+OpenFile* IO_ParseLDFile (str path);
+LDObject* ParseLine (str zLine);
+
+extern vector<OpenFile*> g_LoadedFiles;
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ldforge.kdev4	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,3 @@
+[Project]
+Manager=KDevCustomMakeManager
+Name=ldforge
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ldforge.pro	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,36 @@
+######################################################################
+# Automatically generated by qmake (2.01a) Sat Sep 22 17:29:49 2012
+######################################################################
+
+TEMPLATE = app
+TARGET = 
+DEPENDPATH += .
+INCLUDEPATH += .
+
+# Input
+HEADERS += bbox.h \
+           common.h \
+           draw.h \
+           gui.h \
+           io.h \
+           ldtypes.h \
+           misc.h \
+           model.h \
+           scanner.h \
+           str.h \
+           config.h \
+           cfgdef.h
+SOURCES += bbox.cpp \
+           draw.cpp \
+           gui.cpp \
+           io.cpp \
+           ldtypes.cpp \
+           main.cpp \
+           misc.cpp \
+           model.cpp \
+           scanner.cpp \
+           str.cpp \ 
+           config.cpp
+
+QMAKE_CXXFLAGS += -std=c++0x
+QT += opengl
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ldtypes.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,62 @@
+#include "common.h"
+#include "ldtypes.h"
+
+const char* g_saObjTypeNames[] = {
+	"unidentified",
+	"unknown",
+	"empty",
+	"comment",
+	"subfile",
+	"line",
+	"triangle",
+	"quadrilateral",
+	"condline",
+	"vector",
+	"vertex",
+};
+
+// =============================================================================
+// LDObject constructors
+LDObject::LDObject () {
+	
+}
+
+LDGibberish::LDGibberish () {
+	
+}
+
+LDEmpty::LDEmpty () {
+	
+}
+
+LDComment::LDComment () {
+	
+}
+
+LDSubfile::LDSubfile () {
+	
+}
+
+LDLine::LDLine () {
+	
+}
+
+LDTriangle::LDTriangle () {
+	
+}
+
+LDQuad::LDQuad () {
+	
+}
+
+LDCondLine::LDCondLine () {
+	
+}
+
+LDVector::LDVector () {
+	
+}
+
+LDVertex::LDVertex () {
+	
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ldtypes.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,208 @@
+#ifndef __LDTYPES_H__
+#define __LDTYPES_H__
+
+#include "common.h"
+#include "str.h"
+
+#define IMPLEMENT_LDTYPE(N) \
+	LD##N (); \
+	virtual LDObjectType_e getType () const { \
+		return OBJ_##N; \
+	}
+
+// =============================================================================
+// LDObjectType_e
+// 
+// Object type codes
+// =============================================================================
+enum LDObjectType_e {
+	OBJ_Unidentified,	// Object is uninitialized (LDObject)
+	OBJ_Gibberish,		// Object is unknown (LDUnknown; bad code number)
+	OBJ_Empty,			// Object represents an empty line (LDEmpty)
+	OBJ_Comment,		// Object represents a comment (LDComment, code: 0)
+	OBJ_Subfile,		// Object represents a sub-file reference (LDSubfile, code: 1)
+	OBJ_Line,			// Object represents a line (LDLine, code: 2)
+	OBJ_Triangle,		// Object represents a triangle (LDTriangle, code: 3)
+	OBJ_Quad,			// Object represents a quadrilateral (LDQuad, code: 4)
+	OBJ_CondLine,		// Object represents a conditional line (LDCondLine, code: 5)
+	OBJ_Vector,			// Object is a vector, LDForge extension object (LDVector)
+	OBJ_Vertex			// Object is a vertex, LDForge extension object (LDVertex)
+};
+
+// =============================================================================
+// 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:
+	LDObject ();
+	
+	virtual LDObjectType_e getType () const {
+		return OBJ_Unidentified;
+	};
+};
+
+// =============================================================================
+// 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)
+	
+	// Content of this unknown line
+	str zContent;
+};
+
+// =============================================================================
+// LDEmptyLine 
+// 
+// Represents an empty line in the LDraw code file.
+// =============================================================================
+class LDEmpty : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Empty)
+};
+
+// =============================================================================
+// 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)
+	
+	str zText; // The text of this comment
+};
+
+// =============================================================================
+// LDSubfile
+//
+// Represents a single code-1 subfile reference.
+// =============================================================================
+class LDSubfile : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Subfile)
+	
+	short dColor; // Color used by the reference
+	vertex vPosition; // Position of the subpart
+	double faMatrix[9]; // Transformation matrix for the subpart
+	str zFileName; // Filename of the subpart
+};
+
+// =============================================================================
+// 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)
+	
+	short dColor; // Color of this line
+	vertex vaCoords[2]; // End points of this line
+};
+
+// =============================================================================
+// 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)
+	
+	vertex vaControl[2]; // Control points
+};
+
+// =============================================================================
+// 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)
+	
+	short dColor;
+	vertex vaCoords[3];
+	
+	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)
+	
+	short dColor;
+	vertex vaCoords[4];
+};
+
+// =============================================================================
+// LDVector
+// 
+// The vector is a LDForge-specific extension. It is essentially a line with an
+// alternative definition. Instead of being defined with two vertices, the vector
+// is defined with a single vertex, a length and a bearing. This makes it useful
+// for dealing with lines positioned on arbitrary angles without fear of precision
+// loss of vertex coordinates. A vector can be transformed into a line and vice
+// versa. Vectors are not a part of official LDraw standard and should be
+// converted to lines for finished parts.
+// 
+// TODO: should a conditional vector be considered?
+// =============================================================================
+class LDVector : public LDObject {
+public:
+	IMPLEMENT_LDTYPE (Vector)
+	
+	vertex vPos;
+	bearing gAngle3D;
+	ulong ulLength;
+};
+
+// =============================================================================
+// LDVertex
+// 
+// The vertex is another LDForce-specific extension. It 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)
+	
+	vertex vPosition;
+};
+
+// =============================================================================
+// 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[];
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,55 @@
+#include <QApplication>
+#include "gui.h"
+#include "io.h"
+#include "bbox.h"
+
+vector<OpenFile*> g_LoadedFiles;
+OpenFile* g_CurrentFile = NULL;
+LDForgeWindow* g_qWindow = NULL; 
+bbox g_BBox;
+
+int main (int argc, char** argv) {
+	g_CurrentFile = IO_ParseLDFile ("55966.dat");
+	g_BBox.calculate();
+	
+	printf ("bbox: (%.3f, %.3f, %.3f), (%.3f, %.3f, %.3f)\n",
+		FVERTEX (g_BBox.v0), FVERTEX (g_BBox.v1));
+	printf ("%u objects\n", g_CurrentFile->objects.size());
+	
+	QApplication app (argc, argv);
+	LDForgeWindow* win = new LDForgeWindow;
+	g_qWindow = win;
+	g_qWindow->buildObjList ();
+	win->show ();
+	return app.exec ();
+}
+
+vertex vertex::midpoint (vertex& other) {
+	vertex mid;
+	mid.x = (x + other.x);
+	mid.y = (y + other.y);
+	mid.z = (z + other.z);
+	return mid;
+}
+
+static str getCoordinateRep (double fCoord) {
+	str zRep = str::mkfmt ("%.3f", 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;
+}
+
+str vertex::getStringRep () {
+	return str::mkfmt ("(%s, %s, %s)",
+		getCoordinateRep (x).chars(),
+		getCoordinateRep (y).chars(),
+		getCoordinateRep (z).chars());
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,37 @@
+#include "common.h"
+#include <math.h>
+
+double GetWordFloat (str& s, int n) {
+	return atof ((s / " ")[n]);
+}
+
+long GetWordInt (str& s, int n) {
+	return atol ((s / " ")[n]);
+}
+
+vertex ParseVertex (str& s, int n) {
+	vertex v;
+	v.x = GetWordFloat (s, n);
+	v.y = GetWordFloat (s, n+1);
+	v.z = GetWordFloat (s, n+2);
+	
+	return v;
+}
+
+void StripWhitespace (str& s) {
+	str other;
+	
+	for (size_t i = 0; i < ~s; i++)
+		if (s[i] > 32 && s[i] < 127)
+			other += s[i];
+}
+
+vertex bearing::project (vertex& vSource, ulong ulLength) {
+	vertex vDest = vSource;
+	
+	vDest.x += (cos (fAngle) * ulLength);
+	vDest.y += (sin (fAngle) * ulLength);
+	vDest.x += (cos (fPitch) * ulLength);
+	vDest.z += (sin (fPitch) * ulLength);
+	return vDest;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,16 @@
+#ifndef __MISC_H__
+#define __MISC_H__
+
+#include "common.h"
+#include "str.h"
+
+inline str GetWord (str& zString, ulong ulIndex) {
+	return (zString / " ")[ulIndex];
+}
+
+double GetWordFloat (str& s, int n);
+long GetWordInt (str& s, int n);
+vertex ParseVertex (str& s, int n);
+void StripWhitespace (str& s);
+
+#endif // __MISC_H__
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/moc_draw.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,81 @@
+/****************************************************************************
+** Meta object code from reading C++ file 'draw.h'
+**
+** Created: Fri Mar 15 18:10:06 2013
+**      by: The Qt Meta Object Compiler version 63 (Qt 4.8.3)
+**
+** WARNING! All changes made in this file will be lost!
+*****************************************************************************/
+
+#include "draw.h"
+#if !defined(Q_MOC_OUTPUT_REVISION)
+#error "The header file 'draw.h' doesn't include <QObject>."
+#elif Q_MOC_OUTPUT_REVISION != 63
+#error "This file was generated using the moc from 4.8.3. It"
+#error "cannot be used with the include files from this version of Qt."
+#error "(The moc has changed too much.)"
+#endif
+
+QT_BEGIN_MOC_NAMESPACE
+static const uint qt_meta_data_renderer[] = {
+
+ // content:
+       6,       // revision
+       0,       // classname
+       0,    0, // classinfo
+       0,    0, // methods
+       0,    0, // properties
+       0,    0, // enums/sets
+       0,    0, // constructors
+       0,       // flags
+       0,       // signalCount
+
+       0        // eod
+};
+
+static const char qt_meta_stringdata_renderer[] = {
+    "renderer\0"
+};
+
+void renderer::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
+{
+    Q_UNUSED(_o);
+    Q_UNUSED(_id);
+    Q_UNUSED(_c);
+    Q_UNUSED(_a);
+}
+
+const QMetaObjectExtraData renderer::staticMetaObjectExtraData = {
+    0,  qt_static_metacall 
+};
+
+const QMetaObject renderer::staticMetaObject = {
+    { &QGLWidget::staticMetaObject, qt_meta_stringdata_renderer,
+      qt_meta_data_renderer, &staticMetaObjectExtraData }
+};
+
+#ifdef Q_NO_DATA_RELOCATION
+const QMetaObject &renderer::getStaticMetaObject() { return staticMetaObject; }
+#endif //Q_NO_DATA_RELOCATION
+
+const QMetaObject *renderer::metaObject() const
+{
+    return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
+}
+
+void *renderer::qt_metacast(const char *_clname)
+{
+    if (!_clname) return 0;
+    if (!strcmp(_clname, qt_meta_stringdata_renderer))
+        return static_cast<void*>(const_cast< renderer*>(this));
+    return QGLWidget::qt_metacast(_clname);
+}
+
+int renderer::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
+{
+    _id = QGLWidget::qt_metacall(_c, _id, _a);
+    if (_id < 0)
+        return _id;
+    return _id;
+}
+QT_END_MOC_NAMESPACE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/moc_gui.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,135 @@
+/****************************************************************************
+** Meta object code from reading C++ file 'gui.h'
+**
+** Created: Fri Mar 15 19:46:05 2013
+**      by: The Qt Meta Object Compiler version 63 (Qt 4.8.3)
+**
+** WARNING! All changes made in this file will be lost!
+*****************************************************************************/
+
+#include "gui.h"
+#if !defined(Q_MOC_OUTPUT_REVISION)
+#error "The header file 'gui.h' doesn't include <QObject>."
+#elif Q_MOC_OUTPUT_REVISION != 63
+#error "This file was generated using the moc from 4.8.3. It"
+#error "cannot be used with the include files from this version of Qt."
+#error "(The moc has changed too much.)"
+#endif
+
+QT_BEGIN_MOC_NAMESPACE
+static const uint qt_meta_data_LDForgeWindow[] = {
+
+ // content:
+       6,       // revision
+       0,       // classname
+       0,    0, // classinfo
+      18,   14, // methods
+       0,    0, // properties
+       0,    0, // enums/sets
+       0,    0, // constructors
+       0,       // flags
+       0,       // signalCount
+
+ // slots: signature, parameters, type, tag, flags
+      15,   14,   14,   14, 0x08,
+      26,   14,   14,   14, 0x08,
+      38,   14,   14,   14, 0x08,
+      50,   14,   14,   14, 0x08,
+      64,   14,   14,   14, 0x08,
+      76,   14,   14,   14, 0x08,
+      94,   14,   14,   14, 0x08,
+     109,   14,   14,   14, 0x08,
+     128,   14,   14,   14, 0x08,
+     143,   14,   14,   14, 0x08,
+     162,   14,   14,   14, 0x08,
+     180,   14,   14,   14, 0x08,
+     197,   14,   14,   14, 0x08,
+     214,   14,   14,   14, 0x08,
+     225,   14,   14,   14, 0x08,
+     237,   14,   14,   14, 0x08,
+     250,   14,   14,   14, 0x08,
+     263,   14,   14,   14, 0x08,
+
+       0        // eod
+};
+
+static const char qt_meta_stringdata_LDForgeWindow[] = {
+    "LDForgeWindow\0\0slot_new()\0slot_open()\0"
+    "slot_save()\0slot_saveAs()\0slot_exit()\0"
+    "slot_newSubfile()\0slot_newLine()\0"
+    "slot_newTriangle()\0slot_newQuad()\0"
+    "slot_newCondLine()\0slot_newComment()\0"
+    "slot_newVector()\0slot_newVertex()\0"
+    "slot_cut()\0slot_copy()\0slot_paste()\0"
+    "slot_about()\0slot_aboutQt()\0"
+};
+
+void LDForgeWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
+{
+    if (_c == QMetaObject::InvokeMetaMethod) {
+        Q_ASSERT(staticMetaObject.cast(_o));
+        LDForgeWindow *_t = static_cast<LDForgeWindow *>(_o);
+        switch (_id) {
+        case 0: _t->slot_new(); break;
+        case 1: _t->slot_open(); break;
+        case 2: _t->slot_save(); break;
+        case 3: _t->slot_saveAs(); break;
+        case 4: _t->slot_exit(); break;
+        case 5: _t->slot_newSubfile(); break;
+        case 6: _t->slot_newLine(); break;
+        case 7: _t->slot_newTriangle(); break;
+        case 8: _t->slot_newQuad(); break;
+        case 9: _t->slot_newCondLine(); break;
+        case 10: _t->slot_newComment(); break;
+        case 11: _t->slot_newVector(); break;
+        case 12: _t->slot_newVertex(); break;
+        case 13: _t->slot_cut(); break;
+        case 14: _t->slot_copy(); break;
+        case 15: _t->slot_paste(); break;
+        case 16: _t->slot_about(); break;
+        case 17: _t->slot_aboutQt(); break;
+        default: ;
+        }
+    }
+    Q_UNUSED(_a);
+}
+
+const QMetaObjectExtraData LDForgeWindow::staticMetaObjectExtraData = {
+    0,  qt_static_metacall 
+};
+
+const QMetaObject LDForgeWindow::staticMetaObject = {
+    { &QMainWindow::staticMetaObject, qt_meta_stringdata_LDForgeWindow,
+      qt_meta_data_LDForgeWindow, &staticMetaObjectExtraData }
+};
+
+#ifdef Q_NO_DATA_RELOCATION
+const QMetaObject &LDForgeWindow::getStaticMetaObject() { return staticMetaObject; }
+#endif //Q_NO_DATA_RELOCATION
+
+const QMetaObject *LDForgeWindow::metaObject() const
+{
+    return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
+}
+
+void *LDForgeWindow::qt_metacast(const char *_clname)
+{
+    if (!_clname) return 0;
+    if (!strcmp(_clname, qt_meta_stringdata_LDForgeWindow))
+        return static_cast<void*>(const_cast< LDForgeWindow*>(this));
+    return QMainWindow::qt_metacast(_clname);
+}
+
+int LDForgeWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
+{
+    _id = QMainWindow::qt_metacall(_c, _id, _a);
+    if (_id < 0)
+        return _id;
+    if (_c == QMetaObject::InvokeMetaMethod) {
+        if (_id < 18)
+            qt_static_metacall(this, _c, _id, _a);
+        _id -= 18;
+    }
+    return _id;
+}
+QT_END_MOC_NAMESPACE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/model.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,38 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "common.h"
+#include "io.h"
+#include "gui.h"
+#include "draw.h"
+
+// Clear everything from the model
+void closeModel () {
+	// Remove all loaded files and the objects they contain
+	for (ushort i = 0; i < g_LoadedFiles.size(); i++) {
+		OpenFile* f = g_LoadedFiles[i];
+		
+		for (ushort j = 0; j < f->objects.size(); ++j)
+			delete f->objects[j];
+		
+		delete f;
+	}
+	
+	// Clear the array
+	g_LoadedFiles.clear();
+	g_CurrentFile = NULL;
+	
+	g_qWindow->R->hardRefresh();
+}
+
+void newModel () {
+	// Create a new anonymous file and set it to our current
+	if (g_LoadedFiles.size())
+		closeModel (); // Close any open file first, though
+	
+	OpenFile* f = new OpenFile;
+	f->filename = "";
+	g_LoadedFiles.push_back (f);
+	g_CurrentFile = f;
+	
+	g_qWindow->R->hardRefresh();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/model.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,2 @@
+void closeModel ();
+void newModel ();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scanner.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,409 @@
+/*
+ *	botc source code
+ *	Copyright (C) 2012 Santeri `azimuth` Piippo
+ *	All rights reserved.
+ *	
+ *	Redistribution and use in source and binary forms, with or without
+ *	modification, are permitted provided that the following conditions are met:
+ *	
+ *	1. Redistributions of source code must retain the above copyright notice,
+ *	   this list of conditions and the following disclaimer.
+ *	2. Redistributions in binary form must reproduce the above copyright notice,
+ *	   this list of conditions and the following disclaimer in the documentation
+ *	   and/or other materials provided with the distribution.
+ *	3. Neither the name of the developer nor the names of its contributors may
+ *	   be used to endorse or promote products derived from this software without
+ *	   specific prior written permission.
+ *	4. Redistributions in any form must be accompanied by information on how to
+ *	   obtain complete source code for the software and any accompanying
+ *	   software that uses the software. The source code must either be included
+ *	   in the distribution or be available for no more than the cost of
+ *	   distribution plus a nominal fee, and must be freely redistributable
+ *	   under reasonable conditions. For an executable file, complete source
+ *	   code means the source code for all modules it contains. It does not
+ *	   include source code for modules or files that typically accompany the
+ *	   major components of the operating system on which the executable file
+ *	   runs.
+ *	
+ *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ *	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ *	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *	POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "string.h"
+#include "str.h"
+#include "common.h"
+#include "scanner.h"
+#include "stdarg.h"
+
+#define STORE_POSITION \
+	const bool _atnewline = bAtNewLine; \
+	const ulong ulStoredLineNumber = ulaLineNumber[fc]; \
+	const ulong ulStoredCurChar = ulaCurChar[fc];
+
+#define RESTORE_POSITION \
+	bAtNewLine = _atnewline; \
+	ulaLineNumber[fc] = ulStoredLineNumber; \
+	ulaCurChar[fc] = ulStoredCurChar;
+
+// ============================================================================
+Scanner::Scanner (str path) {
+	token = "";
+	zPrevToken = "";
+	lPrevPos = 0;
+	fc = -1;
+	
+	for (unsigned int u = 0; u < MAX_FILESTACK; u++)
+		fp[u] = NULL;
+	
+	OpenFile (path);
+	dCommentMode = 0;
+}
+
+// ============================================================================
+Scanner::~Scanner () {
+	// If comment mode is 2 by the time the file ended, the
+	// comment was left unterminated. 1 is no problem, since
+	// it's terminated by newlines anyway.
+	if (dCommentMode == 2)
+		ParserError ("unterminated `/*`-style comment");
+	
+	for (unsigned int u = 0; u < MAX_FILESTACK; u++) {
+		if (fp[u]) {
+			ParserWarning ("file idx %u remained open after parsing", u);
+			CloseFile (u);
+		}
+	}
+}
+
+// ============================================================================
+// Opens a file and pushes its pointer to stack
+void Scanner::OpenFile (str path) {
+	if (fc+1 >= MAX_FILESTACK) 
+		ParserError ("supposed to open file `%s` but file stack is full! do you have recursive `#include` directives?",
+			path.chars());
+	
+	// Save the position first.
+	if (fc != -1) {
+		laSavedPos[fc] = ftell (fp[fc]);
+	}
+	
+	fc++;
+	
+	fp[fc] = fopen (path.chars(), "r");
+	if (!fp[fc]) {
+		ParserError ("couldn't open %s for reading!\n", path.chars ());
+		exit (1);
+	}
+	
+	fseek (fp[fc], 0, SEEK_SET);
+	saFilePath[fc] = path.chars();
+	ulaLineNumber[fc] = 1;
+	ulaCurChar[fc] = 1;
+	ulaPosition[fc] = 0;
+	bAtNewLine = 0;
+}
+
+// ============================================================================
+// Closes the current file
+void Scanner::CloseFile (unsigned int u) {
+	if (u >= MAX_FILESTACK)
+		u = fc;
+	
+	if (!fp[u])
+		return;
+	
+	fclose (fp[u]);
+	fp[u] = NULL;
+	fc--;
+	
+	if (fc != -1)
+		fseek (fp[fc], laSavedPos[fc], SEEK_SET);
+}
+
+// ============================================================================
+char Scanner::ReadChar () {
+	if (feof (fp[fc]))
+		return 0;
+	
+	char c;
+	if (!fread (&c, 1, 1, fp[fc]))
+		return 0;
+	
+	// We're at a newline, thus next char read will begin the next line
+	if (bAtNewLine) {
+		bAtNewLine = false;
+		ulaLineNumber[fc]++;
+		ulaCurChar[fc] = 0; // gets incremented to 1
+	}
+	
+	if (c == '\n')
+		bAtNewLine = true;
+	
+	ulaCurChar[fc]++;
+	return c;
+}
+
+// ============================================================================
+// Peeks the next character
+char Scanner::PeekChar (int offset) {
+	// Store current position
+	long curpos = ftell (fp[fc]);
+	STORE_POSITION
+	
+	// Forward by offset
+	fseek (fp[fc], offset, SEEK_CUR);
+	
+	// Read the character
+	char* c = (char*)malloc (sizeof (char));
+	
+	if (!fread (c, sizeof (char), 1, fp[fc])) {
+		fseek (fp[fc], curpos, SEEK_SET);
+		return 0;
+	}
+	
+	// Rewind back
+	fseek (fp[fc], curpos, SEEK_SET);
+	RESTORE_POSITION
+	
+	return c[0];
+}
+
+// ============================================================================
+// Read a token from the file buffer. Returns true if token was found, false if not.
+bool Scanner::Next (bool peek) {
+	lPrevPos = ftell (fp[fc]);
+	str tmp = "";
+	
+	while (1) {
+		// Check end-of-file
+		if (feof (fp[fc])) {
+			// If we're just peeking, we shouldn't
+			// actually close anything.. 
+			if (peek)
+				break;
+			
+			CloseFile ();
+			if (fc == -1)
+				break;
+		}
+		
+		// Check if the next token possibly starts a comment.
+		if (PeekChar () == '/' && !tmp.len()) {
+			char c2 = PeekChar (1);
+			// C++-style comment
+			if (c2 == '/')
+				dCommentMode = 1;
+			else if (c2 == '*')
+				dCommentMode = 2;
+			
+			// We don't need to actually read in the
+			// comment characters, since they will get
+			// ignored due to comment mode anyway.
+		}
+		
+		c = ReadChar ();
+		
+		// If this is a comment we're reading, check if this character
+		// gets the comment terminated, otherwise ignore it.
+		if (dCommentMode > 0) {
+			if (dCommentMode == 1 && c == '\n') {
+				// C++-style comments are terminated by a newline
+				dCommentMode = 0;
+				continue;
+			} else if (dCommentMode == 2 && c == '*') {
+				// C-style comments are terminated by a `*/`
+				if (PeekChar() == '/') {
+					dCommentMode = 0;
+					ReadChar ();
+				}
+			}
+			
+			// Otherwise, ignore it.
+			continue;
+		}
+		
+		// Non-alphanumber characters (sans underscore) break the word too.
+		// If there was prior data, the delimeter pushes the cursor back so
+		// that the next character will be the same delimeter. If there isn't,
+		// the delimeter itself is included (and thus becomes a token itself.)
+		if ((c >= 33 && c <= 47) ||
+			(c >= 58 && c <= 64) ||
+			(c >= 91 && c <= 96 && c != '_') ||
+			(c >= 123 && c <= 126)) {
+			if (tmp.len())
+				fseek (fp[fc], ftell (fp[fc]) - 1, SEEK_SET);
+			else
+				tmp += c;
+			break;
+		}
+		
+		if (c <= 32 || c >= 127) {
+			// Don't break if we haven't gathered anything yet.
+			if (tmp.len())
+				break;
+		} else {
+			tmp += c;
+		}
+	}
+	
+	// If we got nothing here, read failed. This should
+	// only happen in the case of EOF.
+	if (!tmp.len()) {
+		token = "";
+		return false;
+	}
+	
+	ulaPosition[fc]++;
+	zPrevToken = token;
+	token = tmp;
+	return true;
+}
+
+// ============================================================================
+// Returns the next token without advancing the cursor.
+str Scanner::PeekNext (int offset) {
+	// Store current information
+	str storedtoken = token;
+	int cpos = ftell (fp[fc]);
+	STORE_POSITION
+	
+	// Advance on the token.
+	while (offset >= 0) {
+		if (!Next (true))
+			return "";
+		offset--;
+	}
+	
+	str tmp = token;
+	
+	// Restore position
+	fseek (fp[fc], cpos, SEEK_SET);
+	ulaPosition[fc]--;
+	token = storedtoken;
+	RESTORE_POSITION
+	return tmp;
+}
+
+// ============================================================================
+void Scanner::Seek (unsigned int n, int origin) {
+	switch (origin) {
+	case SEEK_SET:
+		fseek (fp[fc], 0, SEEK_SET);
+		ulaPosition[fc] = 0;
+		break;
+	case SEEK_CUR:
+		break;
+	case SEEK_END:
+		printf ("ScriptReader::Seek: SEEK_END not yet supported.\n");
+		break;
+	}
+	
+	for (unsigned int i = 0; i < n+1; i++)
+		Next();
+}
+
+// ============================================================================
+void Scanner::MustNext (const char* c) {
+	if (!Next()) {
+		if (strlen (c))
+			ParserError ("expected `%s`, reached end of file instead\n", c);
+		else
+			ParserError ("expected a token, reached end of file instead\n");
+	}
+	
+	if (strlen (c))
+		MustThis (c);
+}
+
+// ============================================================================
+void Scanner::MustThis (const char* c) {
+	if (token.compare (c) != 0)
+		ParserError ("expected `%s`, got `%s` instead", c, token.chars());
+}
+
+// ============================================================================
+void Scanner::ParserError (const char* message, ...) {
+	PERFORM_FORMAT (message, outmessage);
+	ParserMessage ("\nError: ", outmessage);
+	exit (1);
+}
+
+// ============================================================================
+void Scanner::ParserWarning (const char* message, ...) {
+	PERFORM_FORMAT (message, outmessage);
+	ParserMessage ("Warning: ", outmessage);
+}
+
+// ============================================================================
+void Scanner::ParserMessage (const char* header, char* message) {
+	if (fc >= 0 && fc < MAX_FILESTACK)
+		fprintf (stderr, "%s%s:%lu:%lu: %s\n",
+			header, saFilePath[fc], ulaLineNumber[fc], ulaCurChar[fc], message);
+	else
+		fprintf (stderr, "%s%s\n", header, message);
+}
+
+// ============================================================================
+// if gotquote == 1, the current token already holds the quotation mark.
+void Scanner::MustString (bool gotquote) {
+	if (gotquote)
+		MustThis ("\"");
+	else
+		MustNext ("\"");
+	
+	str string;
+	// Keep reading characters until we find a terminating quote.
+	while (1) {
+		// can't end here!
+		if (feof (fp[fc]))
+			ParserError ("unterminated string");
+		
+		char c = ReadChar ();
+		if (c == '"')
+			break;
+		
+		string += c;
+	}
+	
+	token = string;
+}
+
+// ============================================================================
+void Scanner::MustNumber (bool fromthis) {
+	if (!fromthis)
+		MustNext ();
+	
+	str num = token;
+	if (!num.compare ("-")) {
+		MustNext ();
+		num += token;
+	}
+	
+	// "true" and "false" are valid numbers
+	if (!token.icompare ("true"))
+		token = "1";
+	else if (!token.icompare ("false"))
+		token = "0";
+	else {
+		if (!token.isnumber())
+			ParserError ("expected a number, got `%s`", num.chars());
+		
+		str check;
+		check.appendformat ("%d", atoi (num.chars ()));
+		if (token.compare (check) != 0)
+			ParserWarning ("integer too large: %s -> %s", num.chars(), check.chars());
+		
+		token = num;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scanner.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,102 @@
+/*
+ *	botc source code
+ *	Copyright (C) 2012 Santeri `azimuth` Piippo
+ *	All rights reserved.
+ *	
+ *	Redistribution and use in source and binary forms, with or without
+ *	modification, are permitted provided that the following conditions are met:
+ *	
+ *	1. Redistributions of source code must retain the above copyright notice,
+ *	   this list of conditions and the following disclaimer.
+ *	2. Redistributions in binary form must reproduce the above copyright notice,
+ *	   this list of conditions and the following disclaimer in the documentation
+ *	   and/or other materials provided with the distribution.
+ *	3. Neither the name of the developer nor the names of its contributors may
+ *	   be used to endorse or promote products derived from this software without
+ *	   specific prior written permission.
+ *	4. Redistributions in any form must be accompanied by information on how to
+ *	   obtain complete source code for the software and any accompanying
+ *	   software that uses the software. The source code must either be included
+ *	   in the distribution or be available for no more than the cost of
+ *	   distribution plus a nominal fee, and must be freely redistributable
+ *	   under reasonable conditions. For an executable file, complete source
+ *	   code means the source code for all modules it contains. It does not
+ *	   include source code for modules or files that typically accompany the
+ *	   major components of the operating system on which the executable file
+ *	   runs.
+ *	
+ *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ *	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ *	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *	POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SCRIPTREADER_H__
+#define __SCRIPTREADER_H__
+
+#include <stdio.h>
+#include "str.h"
+
+#define MAX_FILESTACK 8
+#define MAX_SCOPE 32
+#define MAX_CASE 64
+
+#define PERFORM_FORMAT(in, out) \
+	va_list v; \
+	va_start (v, in); \
+	char* out = vdynformat (in, v, 256); \
+	va_end (v);
+
+class Scanner {
+public:
+	// ====================================================================
+	// MEMBERS
+	FILE* fp[MAX_FILESTACK];
+	char* saFilePath[MAX_FILESTACK];
+	int fc;
+	
+	ulong ulaPosition[MAX_FILESTACK];
+	ulong ulaLineNumber[MAX_FILESTACK];
+	ulong ulaCurChar[MAX_FILESTACK];
+	long laSavedPos[MAX_FILESTACK]; // filepointer cursor position
+	str token;
+	short dCommentMode;
+	long lPrevPos;
+	str zPrevToken;
+	
+	// ====================================================================
+	// METHODS
+	Scanner (str path);
+	~Scanner ();
+	void OpenFile (str path);
+	void CloseFile (unsigned int u = MAX_FILESTACK);
+	char ReadChar ();
+	char PeekChar (int offset = 0);
+	bool Next (bool peek = false);
+	void Prev ();
+	str PeekNext (int offset = 0);
+	void Seek (unsigned int n, int origin);
+	void MustNext (const char* c = "");
+	void MustThis (const char* c);
+	void MustString (bool gotquote = false);
+	void MustNumber (bool fromthis = false);
+	void MustBool ();
+	bool BoolValue ();
+	
+	void ParserError (const char* message, ...);
+	void ParserWarning (const char* message, ...);
+	
+private:
+	bool bAtNewLine;
+	char c;
+	void ParserMessage (const char* header, char* message);
+};
+
+#endif // __SCRIPTREADER_H__
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/str.cpp	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,466 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+// #include <initializer_list>
+#include "str.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];
+	clear ();
+	alloclen = strlen (text);
+	append (c);
+}
+
+str::str (char c) {
+	text = new char[1];
+	clear ();
+	alloclen = strlen (text);
+	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 (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::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;
+}
+
+str str::format (...) {
+	va_list va;
+	char* buf;
+	
+	va_start (va, this);
+	buf = vdynformat (text, va, 256);
+	va_end (va);
+	
+	str val = buf;
+	delete[] buf;
+	return val;
+}
+
+// ============================================================================
+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);
+	}
+}
+
+// ============================================================================
+/*
+void str::strip (char c) {
+	strip ({c});
+}
+
+void 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);
+	
+	clear();
+	append (buf);
+	
+	delete[] buf;
+}
+*/
+
+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) {
+	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;
+		
+		res.push_back (substr (a, b));
+		a = b + strlen (del);
+	}
+	
+	// Add the string at the right of the last separator
+	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);}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/str.h	Fri Mar 15 20:11:18 2013 +0200
@@ -0,0 +1,259 @@
+#ifndef __STR_H__
+#define __STR_H__
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+// #include <initializer_list>
+#include <vector>
+
+#ifdef QT_VERSION
+ #include <QString>
+#endif // QT_VERSION
+
+char* vdynformat (const char* csFormat, va_list vArgs, long int lSize);
+
+// 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 ();
+	
+	static str mkfmt (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;
+	}
+	
+	// ======================================================================
+	// 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 (char c);
+	void append (const char* c);
+	void append (str c);
+	
+	// Formats text to the string.
+	void format (const char* fmt, ...);
+	str format (...);
+	
+	// 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);
+	
+	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);
+	
+	/*
+	void strip (char c);
+	void 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 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;
+	}
+	
+#ifdef QT_VERSION
+	operator QString () const {
+		return text;
+	}
+#endif // QT_VERSION
+	
+	operator int () const {
+		return atoi (text);
+	}
+	
+	operator uint () const {
+		return operator int();
+	}
+};
+
+#endif // __STR_H__

mercurial