Converted to 16-bit strings.. again

Fri, 14 Jun 2013 16:00:54 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Fri, 14 Jun 2013 16:00:54 +0300
changeset 286
7a562bf3d829
parent 285
836e77323ab0
child 287
3fcccd8c3357

Converted to 16-bit strings.. again

src/aboutDialog.cpp file | annotate | diff | comparison | revisions
src/addObjectDialog.cpp file | annotate | diff | comparison | revisions
src/colorSelectDialog.cpp file | annotate | diff | comparison | revisions
src/colors.cpp file | annotate | diff | comparison | revisions
src/common.h file | annotate | diff | comparison | revisions
src/config.cpp file | annotate | diff | comparison | revisions
src/config.h file | annotate | diff | comparison | revisions
src/configDialog.cpp file | annotate | diff | comparison | revisions
src/dialogs.cpp file | annotate | diff | comparison | revisions
src/extprogs.cpp file | annotate | diff | comparison | revisions
src/file.cpp file | annotate | diff | comparison | revisions
src/gldraw.cpp file | annotate | diff | comparison | revisions
src/gui.cpp file | annotate | diff | comparison | revisions
src/gui.h file | annotate | diff | comparison | revisions
src/gui_actions.cpp file | annotate | diff | comparison | revisions
src/gui_editactions.cpp file | annotate | diff | comparison | revisions
src/ldtypes.cpp file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/misc.cpp file | annotate | diff | comparison | revisions
src/misc.h file | annotate | diff | comparison | revisions
src/string.cpp file | annotate | diff | comparison | revisions
src/string.h file | annotate | diff | comparison | revisions
src/types.cpp file | annotate | diff | comparison | revisions
src/types.h file | annotate | diff | comparison | revisions
--- a/src/aboutDialog.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/aboutDialog.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -32,9 +32,9 @@
 	icon->setPixmap (getIcon ("ldforge"));
 	
 	// Heading - application label and copyright information
-	QLabel* title = new QLabel (fmt ("<b>" APPNAME " %s</b><br />"
+	QLabel* title = new QLabel (fmt ("<b>" APPNAME " %1</b><br />"
 		"Copyright (C) 2013 Santeri Piippo",
-		fullVersionString ().chars ()));
+		qchars (fullVersionString ())));
 	
 	// Body text
 	QLabel* info = new QLabel (
--- a/src/addObjectDialog.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/addObjectDialog.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -32,6 +32,7 @@
 #include "colorSelectDialog.h"
 #include "history.h"
 #include "widgets.h"
+#include "misc.h"
 
 // =============================================================================
 class SubfileListItem : public QTreeWidgetItem {
@@ -113,9 +114,9 @@
 				QList<QTreeWidgetItem*> subfileItems;
 				
 				str fileName = part.sName;
-				const bool isSubpart = fileName.substr (0, 2) == "s\\";
-				const bool isPrimitive = str (part.sTitle).substr (0, 9) == "Primitive";
-				const bool isHiRes = fileName.substr (0, 3) == "48\\";
+				const bool isSubpart = fileName.mid (0, 2) == "s\\";
+				const bool isPrimitive = str (part.sTitle).mid (0, 9) == "Primitive";
+				const bool isHiRes = fileName.mid (0, 3) == "48\\";
 				
 				if ((i == Subparts && isSubpart) ||
 					(i == Primitives && isPrimitive) ||
@@ -123,7 +124,7 @@
 					(i == Parts && !isSubpart && !isPrimitive && !isHiRes))
 				{
 					SubfileListItem* item = new SubfileListItem (parentItem, j);
-					item->setText (0, fmt ("%s - %s", part.sName, part.sTitle));
+					item->setText (0, fmt ("%1 - %2", part.sName, part.sTitle));
 					subfileItems.append (item);
 				}
 				
@@ -182,11 +183,11 @@
 		break;
 	
 	default:
-		critical (fmt ("Unhandled LDObject type %d (%s) in AddObjectDialog", (int) type, g_saObjTypeNames[type]));
+		critical (fmt ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, g_saObjTypeNames[type]));
 		return;
 	}
 	
-	QPixmap icon = getIcon (fmt ("add-%s", g_saObjTypeIcons[type]));
+	QPixmap icon = getIcon (fmt ("add-%1", g_saObjTypeIcons[type]));
 	LDObject* defaults = LDObject::getDefault (type);
 	
 	lb_typeIcon = new QLabel;
@@ -288,8 +289,8 @@
 	
 	layout->addWidget (makeButtonBox (*this), 5, 0, 1, 4);
 	setLayout (layout);
-	setWindowTitle (fmt (APPNAME ": New %s",
-		g_saObjTypeNames[type]).chars());
+	setWindowTitle (fmt (APPNAME ": New %1",
+		g_saObjTypeNames[type]));
 	
 	setWindowIcon (icon);
 	delete defaults;
@@ -301,9 +302,7 @@
 void AddObjectDialog::setButtonBackground (QPushButton* button, short color) {
 	button->setIcon (getIcon ("palette"));
 	button->setAutoFillBackground (true);
-	button->setStyleSheet (
-		fmt ("background-color: %s", getColor (color)->hexcode.chars()).chars()
-	);
+	button->setStyleSheet (fmt ("background-color: %1", getColor (color)->hexcode));
 }
 
 // =============================================================================
@@ -375,14 +374,14 @@
 	
 	matrix transform = g_identity;
 	if (type == LDObject::Subfile || type == LDObject::Radial) {
-		vector<str> matrixstrvals = str (dlg.le_matrix->text ()).split (" ");
+		vector<str> matrixstrvals = container_cast<QStringList, vector<str>> (str (dlg.le_matrix->text ()).split (" "));
 		
 		if (matrixstrvals.size () == 9) {
 			double matrixvals[9];
 			int i = 0;
 			
 			for (str val : matrixstrvals)
-				matrixvals[i++] = atof (val);
+				matrixvals[i++] = val.toFloat ();
 			
 			transform = matrix (matrixvals);
 		}
@@ -446,12 +445,12 @@
 	case LDObject::Subfile:
 		{
 			str name = dlg.le_subfileName->text ();
-			if (~name == 0)
+			if (name.length () == 0)
 				return; // no subfile filename
 			
 			LDOpenFile* file = getFile (name);
 			if (!file) {
-				critical (fmt ("Couldn't open `%s': %s", name.c (), strerror (errno)));
+				critical (fmt ("Couldn't open `%1': %2", name, strerror (errno)));
 				return;
 			}
 			
--- a/src/colorSelectDialog.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/colorSelectDialog.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -112,12 +112,12 @@
 		
 		if (i == maincolor) {
 			// Use the user preferences for main color here
-			col = gl_maincolor.value.chars ();
+			col = gl_maincolor.value;
 			col.setAlpha (gl_maincolor_alpha * 255.0f);
 		}
 		
 		gs_scene->addRect (x, y, w, w, pen, col);
-		QGraphicsTextItem* numtext = gs_scene->addText (fmt ("%d", i).chars());
+		QGraphicsTextItem* numtext = gs_scene->addText (fmt ("%1", i));
 		numtext->setDefaultTextColor ((luma (col) < 80) ? Qt::white : Qt::black);
 		numtext->setPos (x, y);
 		
@@ -139,8 +139,8 @@
 		return;
 	}
 	
-	lb_colorInfo->setText (fmt ("%d - %s",
-		selColor, col->name.chars()));
+	lb_colorInfo->setText (fmt ("%1 - %2",
+		selColor, col->name));
 }
 
 // =============================================================================
--- a/src/colors.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/colors.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -26,14 +26,14 @@
 static color* g_LDColors[MAX_COLORS];
 
 void initColors () {
-	printf ("%s: initializing color information.\n", __func__);
+	print ("%1: initializing color information.\n", __func__);
 	
 	color* col;
 	
 	// Always make sure there's 16 and 24 available. They're special like that.
 	col = new color;
 	col->hexcode = "#AAAAAA";
-	col->faceColor = col->hexcode.chars ();
+	col->faceColor = col->hexcode;
 	col->edgeColor = Qt::black;
 	g_LDColors[maincolor] = col;
 	
@@ -81,7 +81,7 @@
 	FILE* fp = openLDrawFile ("LDConfig.ldr", false);
 	
 	if (!fp) {
-		critical (fmt ("Unable to open LDConfig.ldr for parsing! (%s)", strerror (errno)));
+		critical (fmt ("Unable to open LDConfig.ldr for parsing! (%1)", strerror (errno)));
 		return;
 	}
 	
@@ -92,7 +92,9 @@
 			continue; // empty or illogical
 		
 		// Use StringParser to parse the LDConfig.ldr file.
-		str line = str (buf).strip ({'\n', '\r'});
+		str line = buf;
+		line.remove ('\r');
+		line.remove ('\n');
 		StringParser pars (line, ' ');
 		
 		short code = 0, alpha = 255;
@@ -113,8 +115,9 @@
 			continue; // not a number
 		
 		// Ensure that the code is within [0 - 511]
-		code = atoi (valuestr);
-		if (code < 0 || code >= 512)
+		bool ok;
+		code = valuestr.toShort (&ok);
+		if (!ok || code < 0 || code >= 512)
 			continue;
 		
 		// VALUE and EDGE tags
@@ -122,15 +125,15 @@
 			continue;
 		
 		// Ensure that our colors are correct
-		QColor faceColor (facename.chars()),
-			edgeColor (edgename.chars());
+		QColor faceColor (facename),
+			edgeColor (edgename);
 		
 		if (!faceColor.isValid () || !edgeColor.isValid ())
 			continue;
 		
 		// Parse alpha if given.
 		if (parseLDConfigTag (pars, "ALPHA", valuestr))
-			alpha = clamp<short> (atoi (valuestr), 0, 255);
+			alpha = clamp<short> (valuestr.toShort (), 0, 255);
 		
 		color* col = new color;
 		col->name = name;
@@ -139,7 +142,6 @@
 		col->hexcode = facename;
 		col->faceColor.setAlpha (alpha);
 		col->index = code;
-		
 		g_LDColors[code] = col;
 	}
 	
--- a/src/common.h	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/common.h	Fri Jun 14 16:00:54 2013 +0300
@@ -59,6 +59,10 @@
 # define devf(...)
 #endif // RELEASE
 
+class QFile;
+extern QFile g_file_stdout;
+extern QFile g_file_stderr;
+
 void doDevf (const char* func, const char* fmtstr, ...);
 
 // Version string identifier
@@ -139,6 +143,17 @@
 #define FUNCNAME __func__
 #endif // __GNUC__
 
+// printf replacement
+#ifndef IN_IDE_PARSER
+#define print(...) doPrint (g_file_stdout, {__VA_ARGS__})
+#define fprint(F, ...) doPrint (F, {__VA_ARGS__})
+#else
+void print (const char* fmtstr, ...);
+void fprint (QFile& f, const char* fmtstr, ...);
+#endif
+void doPrint (QFile& f, initlist<StringFormatArg> args);
+void doPrint (FILE* f, initlist<StringFormatArg> args);
+
 // Replace assert with a version that shows a GUI dialog if possible
 #ifdef assert
 #undef assert
--- a/src/config.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/config.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -19,6 +19,7 @@
 #include <errno.h>
 #include <time.h>
 #include <QDir>
+#include <qtextstream.h>
 #include "common.h"
 #include "config.h"
 #include "misc.h"
@@ -27,33 +28,6 @@
 config* g_configPointers[MAX_CONFIG];
 static ushort g_cfgPointerCursor = 0;
 
-// =============================================================================
-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",
@@ -66,39 +40,38 @@
 // =============================================================================
 // Load the configuration from file
 bool config::load () {
-	printf ("config::load: Loading configuration file...\n");
-	printf ("config::load: Path to configuration is %s\n", filepath ().chars ());
+	print ("config::load: Loading configuration file...\n");
+	print ("config::load: Path to configuration is %1\n", filepath ());
 	
 	// Locale must be disabled for atof
 	setlocale (LC_NUMERIC, "C");
 	
-	FILE* fp = fopen (filepath ().chars(), "r");
-	char linedata[MAX_INI_LINE];
-	char* line;
+	QFile f (filepath ());
+	if (!f.open (QIODevice::ReadOnly))
+		return false;
+	
+	QTextStream input (&f);
 	size_t ln = 0;
 	
-	if (!fp)
-		return false; // can't open for reading
-	
 	// Read the values.
-	while (fgets (linedata, MAX_INI_LINE, fp)) {
+	while (!input.atEnd ()) {
+		str line = input.readLine ().simplified ();
 		ln++;
-		line = linedata;
 		
-		while (*line != 0 && (*line <= 32 || *line >= 127))
-			line++; // Skip junk
-		
-		if (*line == '\0' || line[0] == '#')
+		if (line.isEmpty () || line[0] == '#')
 			continue; // Empty line or comment.
 		
 		// Find the equals sign.
-		char* equals = strchr (line, '=');
-		if (!equals) {
-			fprintf (stderr, "couldn't find `=` sign in entry `%s`\n", line);
+		int equals = line.indexOf ('=');
+		if (equals == -1) {
+			fprint (stderr, "couldn't find `=` sign in entry `%1`\n", line);
 			continue;
 		}
 		
-		str entry = str (line).substr (0, equals - line);
+		str entry = line.left (equals);
+		str valstring = line.right (line.length () - equals - 1);
+		
+		print ("config: `%1` -> %2 == %3 (%4)\n", line, entry, valstring, equals);
 		
 		// Find the config entry for this.
 		config* cfg = null;
@@ -111,24 +84,13 @@
 		}
 		
 		if (!cfg) {
-			fprintf (stderr, "unknown config `%s`\n", entry.chars());
+			fprint (stderr, "unknown config `%1`\n", entry);
 			continue;
 		}
 		
-		str valstring = str (line).substr (equals - line + 1, -1);
-		
-		// Trim the crap off the end
-		while (~valstring) {
-			char c = valstring[~valstring - 1];
-			if (c <= 32 || c >= 127) {
-				valstring -= 1;
-			} else
-				break;
-		}
-		
 		switch (cfg->getType()) {
 		case CONFIG_int:
-			static_cast<intconfig*> (cfg)->value = atoi (valstring.chars());
+			static_cast<intconfig*> (cfg)->value = valstring.toInt ();
 			break;
 		
 		case CONFIG_str:
@@ -136,22 +98,22 @@
 			break;
 		
 		case CONFIG_float:
-			static_cast<floatconfig*> (cfg)->value = atof (valstring.chars());
+			static_cast<floatconfig*> (cfg)->value = valstring.toFloat ();
 			break;
 		
 		case CONFIG_bool:
 		{
 			bool& val = static_cast<boolconfig*> (cfg)->value;
 			
-			if (+valstring == "TRUE" || valstring == "1")
+			if (valstring.toUpper () == "TRUE" || valstring == "1")
 				val = true;
-			else if (+valstring == "FALSE" || valstring == "0")
+			else if (valstring.toUpper () == "FALSE" || valstring == "0")
 				val = false;
 			break;
 		}
 		
 		case CONFIG_keyseq:
-			static_cast<keyseqconfig*> (cfg)->value = keyseq::fromString (valstring.chars ());
+			static_cast<keyseqconfig*> (cfg)->value = keyseq::fromString (valstring);
 			break;
 		
 		default:
@@ -159,26 +121,11 @@
 		}
 	}
 	
-	fclose (fp);
+	f.close ();
 	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 = dynafmt (fmt, va, 256);
-	va_end (va);
-	
-	size_t len = fwrite (buf, 1, strlen (buf), fp);
-	delete[] buf;
-	
-	return len;
-}
-
-// =============================================================================
 // Save the configuration to disk
 bool config::save () {
 	// The function will write floats, disable the locale now so that they
@@ -187,33 +134,22 @@
 	
 	// If the directory doesn't exist, create it now.
 	if (QDir (dirpath ()).exists () == false) {
-		fprintf (stderr, "Creating config path %s...\n", dirpath().chars());
-		if (!QDir ().mkpath (dirpath ().chars ())) {
-			critical ("Failed to create the directory. Configuration cannot be saved!\n");
-			return false; // Couldn't create directory
+		fprint (stderr, "Creating config path %1...\n", dirpath ());
+		if (!QDir ().mkpath (dirpath ())) {
+			critical ("Failed to create the configuration directory. Configuration cannot be saved!\n");
+			return false;
 		}
 	}
 	
-	FILE* fp = fopen (filepath ().chars (), "w");
-	printf ("writing cfg to %s\n", filepath().chars());
+	QFile f (filepath ());
+	print ("writing cfg to %1\n", qchars (filepath ()));
 	
-	if (!fp) {
-		critical (fmt ("Cannot save configuration, cannot open %s for writing\n", filepath ().chars ()));
+	if (!f.open (QIODevice::WriteOnly)) {
+		critical (fmt ("Cannot save configuration, cannot open %1 for writing\n", filepath ()));
 		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);
+	fprint (f, "# Configuration file for " APPNAME "\n");
 	
 	for (config* cfg : g_configPointers) {
 		if (!cfg)
@@ -222,7 +158,7 @@
 		str valstring;
 		switch (cfg->getType()) {
 		case CONFIG_int:
-			valstring.format ("%d", static_cast<intconfig*> (cfg)->value);
+			valstring = fmt ("%1", static_cast<intconfig*> (cfg)->value);
 			break;
 		
 		case CONFIG_str:
@@ -230,7 +166,7 @@
 			break;
 		
 		case CONFIG_float:
-			valstring.format ("%s", ftoa (static_cast<floatconfig*> (cfg)->value).c ());
+			valstring = fmt ("%1", static_cast<floatconfig*> (cfg)->value);
 			break;
 		
 		case CONFIG_bool:
@@ -249,11 +185,11 @@
 			qchars (static_cast<keyseqconfig*> (cfg)->defval.toString ());
 		
 		// Write the entry now.
-		writef (fp, "\n# [%s] default: %s\n", g_ConfigTypeNames[cfg->getType()], defstr);
-		writef (fp, "%s=%s\n", cfg->name, valstring.chars());
+		fprint (f, "\n# [%1] default: %2\n", g_ConfigTypeNames[cfg->getType()], defstr);
+		fprint (f, "%1=%2\n", cfg->name, valstring);
 	}
 	
-	fclose (fp);
+	f.close ();
 	return true;
 }
 
@@ -269,20 +205,18 @@
 
 // =============================================================================
 str config::filepath () {
-	str path;
-	path.format ("%s%s.cfg", dirpath ().chars (),
-		str (APPNAME).lower ().chars ());
+	str path = fmt ("%1%2.cfg", dirpath (),
+		str (APPNAME).toLower ());
 	return path;
 }
 
 // =============================================================================
 str config::dirpath () {
 #ifndef _WIN32
-	return fmt ("%s" DIRSLASH ".%s" DIRSLASH,
-		qchars (QDir::homePath ()),
-		str (APPNAME).lower ().chars ());
+	return fmt ("%1" DIRSLASH ".%2" DIRSLASH,
+		QDir::homePath (), str (APPNAME).toLower ());
 #else
-	return fmt ("%s" DIRSLASH APPNAME DIRSLASH, qchars (QDir::homePath ()));
+	return fmt ("%1" DIRSLASH APPNAME DIRSLASH, QDir::homePath ());
 #endif // _WIN32
 }
 
--- a/src/config.h	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/config.h	Fri Jun 14 16:00:54 2013 +0300
@@ -171,15 +171,10 @@
 	DEFINE_COMPARE_OPERATOR (str, !=)
 	DEFINE_ASSIGN_OPERATOR (str, =)
 	DEFINE_ASSIGN_OPERATOR (str, +=)
-	DEFINE_CAST_OPERATOR (const char*)
 	
-	char operator[] (size_t n) {
+	qchar operator[] (int n) {
 		return value[n];
 	}
-	
-	operator QString () {
-		return QString (value.chars());
-	}
 };
 
 // =============================================================================
--- a/src/configDialog.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/configDialog.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -283,10 +283,10 @@
 	for (int i = 0; i < g_NumGrids; ++i) {
 		// Icon
 		lb_gridIcons[i] = new QLabel;
-		lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%s", str (g_GridInfo[i].name).lower ().chars ())));
+		lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%1", str (g_GridInfo[i].name).toLower ())));
 		
 		// Text label
-		lb_gridLabels[i] = new QLabel (fmt ("%s:", g_GridInfo[i].name));
+		lb_gridLabels[i] = new QLabel (fmt ("%1:", g_GridInfo[i].name));
 		
 		QHBoxLayout* labellayout = new QHBoxLayout;
 		labellayout->addWidget (lb_gridIcons[i]);
@@ -509,16 +509,15 @@
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-void ConfigDialog::pickColor (strconfig& cfg, QPushButton* qButton) {
-	QColorDialog dlg (QColor (cfg.value.chars()));
-	dlg.setWindowIcon (getIcon ("colorselect"));
+void ConfigDialog::pickColor (strconfig& conf, QPushButton* button) {
+	QColor col = QColorDialog::getColor (QColor (conf));
 	
-	if (dlg.exec ()) {
-		uchar r = dlg.currentColor ().red (),
-			g = dlg.currentColor ().green (),
-			b = dlg.currentColor ().blue ();
-		cfg.value.format ("#%.2X%.2X%.2X", r, g, b);
-		setButtonBackground (qButton, cfg.value);
+	if (col.isValid ()) {
+		uchar r = col.red (),
+			g = col.green (),
+			b = col.blue ();
+		conf.value.sprintf ("#%.2X%.2X%.2X", r, g, b);
+		setButtonBackground (button, conf.value);
 	}
 }
 
@@ -533,12 +532,10 @@
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-void ConfigDialog::setButtonBackground (QPushButton* qButton, str zValue) {
-	qButton->setIcon (getIcon ("colorselect"));
-	qButton->setAutoFillBackground (true);
-	qButton->setStyleSheet (
-		fmt ("background-color: %s", zValue.chars()).chars()
-	);
+void ConfigDialog::setButtonBackground (QPushButton* button, str value) {
+	button->setIcon (getIcon ("colorselect"));
+	button->setAutoFillBackground (true);
+	button->setStyleSheet (fmt ("background-color: %1", value));
 }
 
 // =============================================================================
@@ -631,8 +628,8 @@
 	filter = "Applications (*.exe)(*.exe);;All files (*.*)(*.*)";
 #endif // WIN32
 	
-	str fpath = QFileDialog::getOpenFileName (this, fmt ("Path to %s", info->name), "", filter);
-	if (!~fpath)
+	str fpath = QFileDialog::getOpenFileName (this, fmt ("Path to %1", info->name), "", filter);
+	if (fpath.length () == 0)
 		return;
 	
 	info->input->setText (fpath);
@@ -641,12 +638,12 @@
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-void ConfigDialog::setShortcutText (QListWidgetItem* qItem, actionmeta meta) {
+void ConfigDialog::setShortcutText (QListWidgetItem* item, actionmeta meta) {
 	QAction* const act = *meta.qAct;
-	str zLabel = act->iconText ();
-	str zKeybind = act->shortcut ().toString ();
+	str label = act->iconText ();
+	str keybind = act->shortcut ().toString ();
 	
-	qItem->setText (fmt ("%s (%s)", zLabel.chars () ,zKeybind.chars ()).chars());
+	item->setText (fmt ("%1 (%2)", label, keybind));
 }
 
 // =============================================================================
@@ -656,13 +653,13 @@
 	str val;
 	
 	for (quickColor entry : quickColorMeta) {
-		if (~val > 0)
+		if (val.length () > 0)
 			val += ':';
 		
 		if (entry.bSeparator)
 			val += '|';
 		else
-			val += fmt ("%d", entry.col->index);
+			val += fmt ("%1", entry.col->index);
 	}
 	
 	return val;
@@ -757,7 +754,7 @@
 	if (seq == QKeySequence ())
 		shortcut = "&lt;empty&gt;";
 	
-	str text = fmt ("<center><b>%s</b></center>", shortcut.chars ());
+	str text = fmt ("<center><b>%1</b></center>", shortcut);
 	
 	lb_output->setText (text);
 }
--- a/src/dialogs.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/dialogs.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -242,11 +242,11 @@
 }
 
 void SetContentsDialog::setObject (LDObject* obj) {
-	le_contents->setText (obj->raw ().chars());
+	le_contents->setText (obj->raw ());
 	
 	if (obj->getType() == LDObject::Gibberish) {
-		lb_error->setText (fmt ("<span style=\"color: #900\">%s</span>",
-			static_cast<LDGibberish*> (obj)->reason.chars()));
+		lb_error->setText (fmt ("<span style=\"color: #900\">%1</span>",
+			static_cast<LDGibberish*> (obj)->reason));
 		
 		QPixmap errorPixmap = getIcon ("error").scaledToHeight (16);
 		lb_errorIcon->setPixmap (errorPixmap);
@@ -326,7 +326,7 @@
 void LDrawPathDialog::slot_findPath () {
 	str newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path");
 	
-	if (~newpath > 0 && newpath != filename ()) {
+	if (newpath.length () > 0 && newpath != filename ()) {
 		setPath (newpath);
 		slot_tryConfigure ();
 	}
@@ -338,8 +338,8 @@
 
 void LDrawPathDialog::slot_tryConfigure () {
 	if (LDPaths::tryConfigure (filename ()) == false) {
-		lb_resolution->setText (fmt ("<span style=\"color:red; font-weight: bold;\">%s</span>",
-			LDPaths::getError().chars ()));
+		lb_resolution->setText (fmt ("<span style=\"color:red; font-weight: bold;\">%1</span>",
+			LDPaths::getError()));
 		okButton ()->setEnabled (false);
 		return;
 	}
@@ -418,11 +418,11 @@
 	
 	*g_curfile << new LDComment (dlg.le_name->text ());
 	*g_curfile << new LDComment ("Name: <untitled>.dat");
-	*g_curfile << new LDComment (fmt ("Author: %s", author.chars()));
+	*g_curfile << new LDComment (fmt ("Author: %1", author));
 	*g_curfile << new LDComment (fmt ("!LDRAW_ORG Unofficial_Part"));
 	
 	if (license != null)
-		*g_curfile << new LDComment (fmt ("!LICENSE %s", license));
+		*g_curfile << new LDComment (fmt ("!LICENSE %1", license));
 	
 	*g_curfile << new LDEmpty;
 	*g_curfile << new LDBFC (BFCType);
--- a/src/extprogs.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/extprogs.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -52,14 +52,14 @@
 
 // =============================================================================
 static bool checkProgPath (str path, const extprog prog) {
-	if (~path)
+	if (path.length () > 0)
 		return true;
 	
 	const char* name = g_extProgNames[prog];
 	
-	critical (fmt ("Couldn't run %s as no path has "
+	critical (fmt ("Couldn't run %1 as no path has "
 		"been defined for it. Use the configuration dialog's External Programs "
-		"tab to define a path for %s.", name, name));
+		"tab to define a path for %1.", name));
 	return false;
 }
 
@@ -70,25 +70,25 @@
 	
 	switch (proc.error ()) {
 	case QProcess::FailedToStart:
-		errmsg = fmt ("Failed to launch %s. Check that you have set the proper path "
-			"to %s and that you have the proper permissions to launch it.", name, name);
+		errmsg = fmt ("Failed to launch %1. Check that you have set the proper path "
+			"to %1 and that you have the proper permissions to launch it.", name);
 		break;
 	
 	case QProcess::Crashed:
-		errmsg = fmt ("%s crashed.", name);
+		errmsg = fmt ("%1 crashed.", name);
 		break;
 	
 	case QProcess::WriteError:
 	case QProcess::ReadError:
-		errmsg = fmt ("I/O error while interacting with %s.", name);
+		errmsg = fmt ("I/O error while interacting with %1.", name);
 		break;
 	
 	case QProcess::UnknownError:
-		errmsg = fmt ("Unknown error occurred while executing %s.", name);
+		errmsg = fmt ("Unknown error occurred while executing %1.", name);
 		break;
 	
 	case QProcess::Timedout:
-		errmsg = fmt ("%s timed out.", name);
+		errmsg = fmt ("%1 timed out.", name);
 		break;
 	}
 	
@@ -121,28 +121,21 @@
 			for (LDObject* obj : objs)
 				delete obj;
 		} else {
-			str line = fmt ("%s\r\n", obj->raw ().chars ());
-			fwrite (line.chars(), 1, ~line, fp);
+			str line = obj->raw () + "\r\n";
+			fwrite (qchars (line), 1, line.length (), fp);
 		}
 	}
 }
 
 void writeObjects (vector<LDObject*>& objects, str fname) {
 	// Write the input file
-	FILE* fp = fopen (fname, "w");
+	FILE* fp = fopen (qchars (fname), "w");
 	if (!fp) {
-		critical (fmt ("Couldn't open temporary file %s for writing.\n", fname.chars ()));
+		critical (fmt ("Couldn't open temporary file %1 for writing.\n", fname));
 		return;
 	}
 	
 	writeObjects (objects, fp);
-	
-#if 0
-	ushort idx = rand ();
-	printf ("%s -> debug_%u\n", fname.chars (), idx);
-	QFile::copy (fname.chars (), fmt ("debug_%u", idx));
-#endif // 0
-	
 	fclose (fp);
 }
 
@@ -170,7 +163,7 @@
 	str inputname, outputname;
 	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
 	
-	printf ("cmdline: %s %s\n", path.chars (), qchars (argvstr));
+	printf ("cmdline: %s %s\n", qchars (path), qchars (argvstr));
 	
 	if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname))
 		return;
@@ -178,7 +171,7 @@
 	QProcess proc;
 	
 	// Init stdin
-	FILE* stdinfp = fopen (inputname, "w");
+	FILE* stdinfp = fopen (qchars (inputname), "w");
 	
 	// Begin!
 	proc.setStandardInputFile (inputname);
@@ -210,9 +203,9 @@
 #endif // RELEASE
 	
 	// Read the output file
-	FILE* fp = fopen (fname, "r");
+	FILE* fp = fopen (qchars (fname), "r");
 	if (!fp) {
-		critical (fmt ("Couldn't open temporary file %s for reading.\n", fname.chars ()));
+		critical (fmt ("Couldn't open temporary file %1 for reading.\n", fname));
 		return;
 	}
 	
@@ -288,10 +281,15 @@
 		return;
 	
 	// Compose the command-line arguments
-	str argv = fmt ("%s %s %f -a %f %s %s",
+	str argv = join ({
 		(axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
 		(mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
-		depth, condAngle, inDATName.chars (), outDATName.chars ());
+		depth,
+		"-a",
+		condAngle,
+		inDATName,
+		outDATName
+	});
 	
 	writeSelection (inDATName);
 	runUtilityProcess (Ytruder, prog_ytruder, argv);
@@ -345,12 +343,16 @@
 		return;
 	
 	// Compose arguments
-	str argv = fmt ("%s %s %s %s -t %f %s %s",
+	str argv = join ({
 		(condense == false) ? "-q" : "",
 		(subst == false) ? "-r" : "",
 		(condlineCheck) ? "-a" : "",
 		(colorize) ? "-c" : "",
-		coplthres, inDATName.chars (), outDATName.chars ());
+		"-t",
+		coplthres,
+		inDATName,
+		outDATName
+	});
 	
 	writeSelection (inDATName);
 	runUtilityProcess (Rectifier, prog_rectifier, argv);
@@ -438,13 +440,26 @@
 		return;
 	}
 	
-	str parms = fmt ("%s %s -s %f",
+	str parms = join ({
 		(cb_colorize->isChecked ()) ? "-c" : "",
 		(cb_nocondense->isChecked ()) ? "-t" : "",
-		dsb_prescale->w ()->value ());
+		"-s",
+		dsb_prescale->w ()->value ()
+	});
 	
-	str argv_normal = fmt ("%s %s %s %s", parms.chars (), inDATName.chars (), cutDATName.chars (), outDATName.chars ());
-	str argv_inverse = fmt ("%s %s %s %s", parms.chars (), cutDATName.chars (), inDATName.chars (), outDAT2Name.chars ());
+	str argv_normal = join ({
+		parms,
+		inDATName,
+		cutDATName,
+		outDATName
+	});
+	
+	str argv_inverse = join ({
+		parms,
+		cutDATName,
+		inDATName,
+		outDAT2Name
+	});
 	
 	writeColorGroup (inCol, inDATName);
 	writeColorGroup (cutCol, cutDATName);
@@ -457,7 +472,7 @@
 	}
 	
 	if (cb_edges->isChecked ()) {
-		runUtilityProcess (Isecalc, prog_isecalc, fmt ("%s %s %s", inDATName.chars (), cutDATName.chars (), edgesDATName.chars ()));
+		runUtilityProcess (Isecalc, prog_isecalc, join ({inDATName, cutDATName, edgesDATName}));
 		insertOutput (edgesDATName, false, {});
 	}
 }
@@ -520,12 +535,15 @@
 	if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
 		return;
 	
-	str argv = fmt ("%s %s %s %s %s %s %s",
+	str argv = join ({
 		(cb_oldsweep->isChecked () ? "-s" : ""),
 		(cb_reverse->isChecked () ? "-r" : ""),
-		(dsb_segsplit->value () != 0 ? fmt ("-l %f", dsb_segsplit->value ()).c () : ""),
-		(sb_bias->value () != 0 ? fmt ("-s %d", sb_bias->value ()).c () : ""),
-		in1DATName.c (), in2DATName.c (), outDATName.c ());
+		(dsb_segsplit->value () != 0 ? fmt ("-l %1", dsb_segsplit->value ()) : ""),
+		(sb_bias->value () != 0 ? fmt ("-s %1", sb_bias->value ()) : ""),
+		in1DATName,
+		in2DATName,
+		outDATName
+	});
 	
 	writeColorGroup (in1Col, in1DATName);
 	writeColorGroup (in2Col, in2DATName);
@@ -567,7 +585,11 @@
 	if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
 		return;
 	
-	str argv = fmt ("%s %s %s", in1DATName.c (), in2DATName.c (), outDATName.c ());
+	str argv = join ({
+		in1DATName,
+		in2DATName,
+		outDATName
+	});
 	
 	writeColorGroup (in1Col, in1DATName);
 	writeColorGroup (in2Col, in2DATName);
--- a/src/file.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/file.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -74,9 +74,9 @@
 			return false;
 		}
 		
-		pathInfo.partsPath = fmt ("%s" DIRSLASH "parts", path.chars ());
-		pathInfo.LDConfigPath = fmt ("%s" DIRSLASH "LDConfig.ldr", path.chars ());
-		pathInfo.primsPath = fmt ("%s" DIRSLASH "p", path.chars ());
+		pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path);
+		pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path);
+		pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path);
 		
 		return true;
 	}
@@ -117,10 +117,10 @@
 
 // =============================================================================
 str dirname (str path) {
-	long lastpos = path.last (DIRSLASH);
+	long lastpos = path.lastIndexOf (DIRSLASH);
 	
 	if (lastpos > 0)
-		return path.substr (0, lastpos);
+		return path.left (lastpos);
 	
 #ifndef _WIN32
 	if (path[0] == DIRSLASH_CHAR)
@@ -132,54 +132,54 @@
 
 // =============================================================================
 str basename (str path) {
-	long lastpos = path.last (DIRSLASH);
+	long lastpos = path.lastIndexOf (DIRSLASH);
 	
-	if (lastpos < (long) path.len () - 1)
-		return path.substr (lastpos + 1, -1);
+	if (lastpos != -1)
+		return path.mid (lastpos + 1);
 	
 	return path;
 }
 
 // =============================================================================
 FILE* openLDrawFile (str relpath, bool subdirs) {
-	printf ("%s: Try to open %s\n", __func__, relpath.c ());
+	printf ("%s: Try to open %s\n", __func__, qchars (relpath));
 	
 #ifndef WIN32
 	relpath.replace ("\\", "/");
 #endif // WIN32
 	
 	if (g_curfile != null) {
-		str partpath = fmt ("%s" DIRSLASH "%s", dirname (g_curfile->name ()).c (), relpath.c ());
-		printf ("try %s\n", partpath.c ());
-		FILE* fp = fopen (partpath, "r");
+		str partpath = fmt ("%1" DIRSLASH "%2", dirname (g_curfile->name ()), relpath);
+		printf ("try %s\n", qchars (partpath));
+		FILE* fp = fopen (qchars (partpath), "r");
 		
 		if (fp != null)
 			return fp;
 	}
 	
-	printf ("try %s\n", relpath.chars ());
-	FILE* fp = fopen (relpath, "r");
+	printf ("try %s\n", qchars (relpath));
+	FILE* fp = fopen (qchars (relpath), "r");
 	str fullPath;
 	
 	if (fp != null)
 		return fp;
 	
-	if (~io_ldpath.value) {
+	if (io_ldpath.value.length () > 0) {
 		// Try with just the LDraw path first
-		fullPath = fmt ("%s" DIRSLASH "%s", io_ldpath.value.chars(), relpath.chars());
-		printf ("try %s\n", fullPath.chars ());
+		fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath);
+		printf ("try %s\n", qchars (fullPath));
 		
-		fp = fopen (fullPath, "r");
+		fp = fopen (qchars (fullPath), "r");
 		if (fp != null)
 			return fp;
 		
 		if (subdirs) {
 			for (auto subdir : initlist<const char*> ({"parts", "p"})) {
-				fullPath = fmt ("%s" DIRSLASH "%s" DIRSLASH "%s",
-					io_ldpath.value.chars(), subdir, relpath.chars());
+				fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3",
+					io_ldpath, subdir, relpath);
 				
-				printf ("try %s\n", fullPath.chars ());
-				fp = fopen (fullPath.chars (), "r");
+				printf ("try %s\n", qchars (fullPath));
+				fp = fopen (qchars (fullPath), "r");
 				
 				if (fp)
 					return fp;
@@ -201,8 +201,9 @@
 	while (fgets (line, sizeof line, filePointer ())) {
 		// Trim the trailing newline
 		str data = line;
-		while (data[~data - 1] == '\n' || data[~data - 1] == '\r')
-			data -= 1;
+		qchar c;
+		while ((c = data[data.length () - 1]) == '\n' || c == '\r')
+			data.chop (1);
 		
 		LDObject* obj = parseLine (data);
 		assert (obj != null);
@@ -210,9 +211,9 @@
 		// Check for parse errors and warn about tthem
 		if (obj->getType () == LDObject::Gibberish) {
 			logf (LOG_Warning, "Couldn't parse line #%lu: %s\n",
-				m_progress + 1, static_cast<LDGibberish*> (obj)->reason.chars());
+				m_progress + 1, qchars (static_cast<LDGibberish*> (obj)->reason));
 			
-			logf (LOG_Warning, "- Line was: %s\n", data.chars());
+			logf (LOG_Warning, "- Line was: %s\n", qchars (data));
 			
 			if (m_warningsPointer)
 				(*m_warningsPointer)++;
@@ -300,21 +301,17 @@
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 LDOpenFile* openDATFile (str path, bool search) {
-	logf ("Opening %s...\n", path.chars());
-	
 	// Convert the file name to lowercase since some parts contain uppercase
 	// file names. I'll assume here that the library will always use lowercase
 	// file names for the actual parts..
 	FILE* fp;
 	if (search)
-		fp = openLDrawFile (-path, true);
+		fp = openLDrawFile (path.toLower (), true);
 	else
-		fp = fopen (path, "r");
+		fp = fopen (qchars (path), "r");
 	
-	if (!fp) {
-		logf (LOG_Error, "Couldn't open %s: %s\n", path.chars (), strerror (errno));
+	if (!fp)
 		return null;
-	}
 	
 	LDOpenFile* oldLoad = g_curfile;
 	LDOpenFile* load = new LDOpenFile;
@@ -341,7 +338,7 @@
 	g_loadedFiles << load;
 	
 	logf ("File %s parsed successfully (%lu warning%s).\n",
-		path.chars(), numWarnings, plural (numWarnings));
+		qchars (path), numWarnings, plural (numWarnings));
 	
 	return load;
 }
@@ -355,25 +352,25 @@
 	// If we have unsaved changes, warn and give the option of saving.
 	if (!implicit () && history ().pos () != savePos ()) {
 		switch (QMessageBox::question (g_win, "Unsaved Changes",
-			fmt ("There are unsaved changes to %s. Should it be saved?",
-			(name ().len () > 0) ? name ().c () : "<anonymous>"),
+			fmt ("There are unsaved changes to %1. Should it be saved?",
+			(name ().length () > 0) ? name () : "<anonymous>"),
 			(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel), QMessageBox::Cancel))
 		{
 		case QMessageBox::Yes:
 			// If we don't have a file path yet, we have to ask the user for one.
-			if (name ().len () == 0) {
+			if (name ().length () == 0) {
 				str newpath = QFileDialog::getSaveFileName (g_win, "Save As",
 					g_curfile->name (), "LDraw files (*.dat *.ldr)");
 				
-				if (newpath.len () == 0)
+				if (newpath.length () == 0)
 					return false;
 				
 				setName (newpath);
 			}
 			
 			if (!save ()) {
-				str errormsg = fmt ("Failed to save %s: %s\nDo you still want to close?",
-					name ().c (), strerror (errno));
+				str errormsg = fmt ("Failed to save %1: %2\nDo you still want to close?",
+					name (), strerror (errno));
 				
 				if (QMessageBox::critical (g_win, "Save Failure", errormsg,
 					(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::No)
@@ -438,28 +435,35 @@
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 void addRecentFile (str path) {
-	vector<str> rfiles = io_recentfiles.value.split ('@');
+	QStringList rfiles = io_recentfiles.value.split ('@');
+	
+	int idx = 0;
 	
-	ulong idx = rfiles.find (path);
+	for (str& it : rfiles) {
+		if (it == path)
+			break;
+		
+		idx++;
+	}
 	
 	// If this file already is in the list, pop it out.
-	if (idx != -1u) {
+	if (idx != rfiles.size ()) {
 		if (rfiles.size () == 1)
 			return; // only recent file - do nothing
 		
 		// Pop it out.
-		rfiles.erase (idx);
+		rfiles.removeAt (idx);
 	}
 	
 	// If there's too many recent files, drop one out.
 	while (rfiles.size () > (5 - 1))
-		rfiles.erase (0);
+		rfiles.removeAt (0);
 	
 	// Add the file
 	rfiles.push_back (path);
 	
 	// Rebuild the config string
-	io_recentfiles = str::join (rfiles, "@");
+	io_recentfiles = rfiles.join ("@");
 	
 	config::save ();
 	g_win->updateRecentFilesMenu ();
@@ -477,7 +481,7 @@
 	if (!file) {
 		// Tell the user loading failed.
 		setlocale (LC_ALL, "C");
-		critical (fmt ("Failed to open %s: %s", path.chars(), strerror (errno)));
+		critical (fmt ("Failed to open %1: %2", path, strerror (errno)));
 		
 		g_loadingMainFile = false;
 		return;
@@ -504,10 +508,10 @@
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 bool LDOpenFile::save (str savepath) {
-	if (!~savepath)
+	if (!savepath.length ())
 		savepath = name ();
 	
-	FILE* fp = fopen (savepath, "w");
+	FILE* fp = fopen (qchars (savepath), "w");
 	
 	if (!fp)
 		return false;
@@ -519,15 +523,15 @@
 	if (!implicit () && objs ().size () >= 2 && object (1)->getType () == LDObject::Comment) {
 		fpathComment = static_cast<LDComment*> (object (1));
 		
-		if (fpathComment->text.substr (0, 6) == "Name: ") {
+		if (fpathComment->text.left (6) == "Name: ") {
 			str newfname;
 			str dir = basename (dirname (savepath));
 			
 			if (dir == "s" || dir == "48")
-				newfname = fmt ("%s\\", dir.c ());
+				newfname = dir + "\\";
 			
 			newfname += basename (savepath);
-			fpathComment->text = fmt ("Name: %s", newfname.c ());
+			fpathComment->text = fmt ("Name: %1", newfname);
 			g_win->buildObjList ();
 		}
 	}
@@ -535,8 +539,8 @@
 	// Write all entries now
 	for (LDObject* obj : objs ()) {
 		// LDraw requires files to have DOS line endings
-		str line = fmt ("%s\r\n", obj->raw ().chars ());
-		fwrite (line.chars(), 1, line.len (), fp);
+		str line = obj->raw () + "\r\n";
+		fwrite (qchars (line), 1, line.length (), fp);
 	}
 	
 	fclose (fp);
@@ -556,13 +560,13 @@
 #define CHECK_TOKEN_NUMBERS(MIN,MAX) \
 	for (ushort i = MIN; i <= MAX; ++i) \
 		if (!isNumber (tokens[i])) \
-			return new LDGibberish (line, fmt ("Token #%u was `%s`, expected a number", \
-				(i + 1), tokens[i].chars()));
+			return new LDGibberish (line, fmt ("Token #%1 was `%2`, expected a number", \
+				(i + 1), tokens[i]));
 
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-static vertex parseVertex (vector<str>& s, const ushort n) {
+static vertex parseVertex (QStringList& s, const ushort n) {
 	// Disable the locale while parsing the line or atof's behavior changes
 	// between locales (i.e. fails to read decimals properly). That is
 	// quite undesired...
@@ -570,7 +574,7 @@
 	
 	vertex v;
 	for (const Axis ax : g_Axes)
-		v[ax] = atof (s[n + ax]);
+		v[ax] = s[n + ax].toFloat ();
 	
 	return v;
 }
@@ -579,23 +583,23 @@
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 LDObject* parseLine (str line) {
-	vector<str> tokens = line.split (" ");
+	QStringList tokens = line.split (" ", str::SkipEmptyParts);
 	
 	if (!tokens.size ()) {
 		// Line was empty, or only consisted of whitespace
 		return new LDEmpty;
 	}
 	
-	if (~tokens[0] != 1)
+	if (tokens[0].length () != 1)
 		return new LDGibberish (line, "Illogical line code");
 	
-	const char c = tokens[0][0];
-	switch (c - '0') {
+	const qchar c = tokens[0][0];
+	switch (c.toAscii () - '0') {
 	case 0:
 		{
 			// Comment
 			str comm;
-			for (uint i = 1; i < tokens.size(); ++i) {
+			for (int i = 1; i < tokens.size(); ++i) {
 				comm += tokens[i];
 				
 				if (i != tokens.size() - 1)
@@ -605,7 +609,7 @@
 			// Handle BFC statements
 			if (tokens.size() > 2 && tokens[1] == "BFC") {
 				for (short i = 0; i < LDBFC::NumStatements; ++i)
-					if (comm == fmt ("BFC %s", LDBFC::statements [i]))
+					if (comm == fmt ("BFC %1", LDBFC::statements [i]))
 						return new LDBFC ((LDBFC::Type) i);
 				
 				// MLCAD is notorious for stuffing these statements in parts it
@@ -624,10 +628,10 @@
 					CHECK_TOKEN_NUMBERS (3, 6)
 					
 					LDVertex* obj = new LDVertex;
-					obj->setColor (atol (tokens[3]));
+					obj->setColor (tokens[3].toLong ());
 					
 					for (const Axis ax : g_Axes)
-						obj->pos[ax] = atof (tokens[4 + ax]); // 4 - 6
+						obj->pos[ax] = tokens[4 + ax].toDouble (); // 4 - 6
 					
 					return obj;
 				}
@@ -639,27 +643,27 @@
 					LDRadial::Type eType = LDRadial::NumTypes;
 					
 					for (int i = 0; i < LDRadial::NumTypes; ++i) {
-						if (str (LDRadial::radialTypeName ((LDRadial::Type) i)).upper ().strip (' ') == tokens[3]) {
+						if (str (LDRadial::radialTypeName ((LDRadial::Type) i)).toUpper ().remove (' ') == tokens[3]) {
 							eType = (LDRadial::Type) i;
 							break;
 						}
 					}
 					
 					if (eType == LDRadial::NumTypes)
-						return new LDGibberish (line, fmt ("Unknown radial type %s", tokens[3].chars ()));
+						return new LDGibberish (line, fmt ("Unknown radial type %1", tokens[3]));
 					
 					LDRadial* obj = new LDRadial;
 					
 					obj->setType (eType);
-					obj->setColor (atol (tokens[4]));
-					obj->setSegments (atol (tokens[5]));
-					obj->setDivisions (atol (tokens[6]));
-					obj->setNumber (atol (tokens[7]));
+					obj->setColor (tokens[4].toLong ());
+					obj->setSegments (tokens[5].toLong ());
+					obj->setDivisions (tokens[6].toLong ());
+					obj->setNumber (tokens[7].toLong ());
 					obj->setPosition (parseVertex (tokens, 8)); // 8 - 10
 					
 					matrix transform;
 					for (short i = 0; i < 9; ++i)
-						transform[i] = atof (tokens[i + 11]); // 11 - 19
+						transform[i] = tokens[i + 11].toDouble (); // 11 - 19
 					
 					obj->setTransform (transform);
 					return obj;
@@ -689,12 +693,12 @@
 				return new LDGibberish (line, "Could not open referred file");
 			
 			LDSubfile* obj = new LDSubfile;
-			obj->setColor (atol (tokens[1]));
+			obj->setColor (tokens[1].toLong ());
 			obj->setPosition (parseVertex (tokens, 2)); // 2 - 4
 			
 			matrix transform;
 			for (short i = 0; i < 9; ++i)
-				transform[i] = atof (tokens[i + 5]); // 5 - 13
+				transform[i] = tokens[i + 5].toDouble (); // 5 - 13
 			
 			obj->setTransform (transform);
 			obj->setFileInfo (load);
@@ -708,7 +712,7 @@
 			
 			// Line
 			LDLine* obj = new LDLine;
-			obj->setColor (atol (tokens[1]));
+			obj->setColor (tokens[1].toLong ());
 			for (short i = 0; i < 2; ++i)
 				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 7
 			return obj;
@@ -721,7 +725,7 @@
 			
 			// Triangle
 			LDTriangle* obj = new LDTriangle;
-			obj->setColor (atol (tokens[1]));
+			obj->setColor (tokens[1].toLong ());
 			
 			for (short i = 0; i < 3; ++i)
 				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 10
@@ -736,7 +740,7 @@
 			
 			// Quadrilateral
 			LDQuad* obj = new LDQuad;
-			obj->setColor (atol (tokens[1]));
+			obj->setColor (tokens[1].toLong ());
 			
 			for (short i = 0; i < 4; ++i)
 				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13
@@ -751,7 +755,7 @@
 			
 			// Conditional line
 			LDCondLine* obj = new LDCondLine;
-			obj->setColor (atol (tokens[1]));
+			obj->setColor (tokens[1].toLong ());
 			
 			for (short i = 0; i < 4; ++i)
 				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13
@@ -858,8 +862,6 @@
 vector<partListEntry> g_PartList;
 
 void initPartList () {
-	logf ("%s: initializing parts.lst\n", __func__);
-	
 	FILE* fp = openLDrawFile ("parts.lst", false);
 	
 	if (!fp)
--- a/src/gldraw.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/gldraw.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -113,8 +113,7 @@
 	
 	// Init camera icons
 	for (const GL::Camera cam : g_Cameras) {
-		str iconname;
-		iconname.format ("camera-%s", str (g_CameraNames[cam]).lower ().c ());
+		str iconname = fmt ("camera-%1", str (g_CameraNames[cam]).toLower ());
 		
 		CameraIcon* info = &g_CameraIcons[cam];
 		info->img = new QPixmap (getIcon (iconname));
@@ -193,7 +192,7 @@
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 QColor GLRenderer::getMainColor () {
-	QColor col (gl_maincolor.value.chars());
+	QColor col (gl_maincolor);
 	
 	if (!col.isValid ())
 		return QColor (0, 0, 0);
@@ -204,7 +203,7 @@
 
 // -----------------------------------------------------------------------------
 void GLRenderer::setBackground () {
-	QColor col (gl_bgcolor.value.chars());
+	QColor col (gl_bgcolor);
 	
 	if (!col.isValid ())
 		return;
@@ -436,9 +435,10 @@
 	cx *= negXFac;
 	cy *= negYFac;
 	
+	str tmp;
 	pos3d = g_origin;
-	pos3d[axisX] = atof (fmt ("%.3f", cx));
-	pos3d[axisY] = atof (fmt ("%.3f", cy));
+	pos3d[axisX] = tmp.sprintf ("%.3f", cx).toDouble ();
+	pos3d[axisY] = tmp.sprintf ("%.3f", cy).toDouble ();
 	pos3d[3 - axisX - axisY] = depthValue ();
 	return pos3d;
 }
@@ -503,8 +503,7 @@
 		}
 		
 		// Paint the coordinates onto the screen.
-		str text = fmt ("X: %s, Y: %s, Z: %s", ftoa (m_hoverpos[X]).chars (),
-			ftoa (m_hoverpos[Y]).chars (), ftoa (m_hoverpos[Z]).chars ());
+		str text = fmt ("X: %1, Y: %2, Z: %3", m_hoverpos[X], m_hoverpos[Y], m_hoverpos[Z]);
 		
 		QFontMetrics metrics = QFontMetrics (font ());
 		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
@@ -600,7 +599,7 @@
 			const ushort margin = 4;
 			
 			str label;
-			label.format ("%s Camera", g_CameraNames[camera ()]);
+			label = fmt ("%1 Camera", g_CameraNames[camera ()]);
 			paint.setPen (m_darkbg ? Qt::white : Qt::black);
 			paint.drawText (QPoint (margin, margin + metrics.ascent ()), label);
 		}
@@ -617,8 +616,7 @@
 				ushort x0 = m_pos.x (),
 					y0 = m_pos.y ();
 				
-				str label;
-				label.format ("%s Camera", g_CameraNames[m_toolTipCamera]);
+				str label = fmt ("%1 Camera", g_CameraNames[m_toolTipCamera]);
 				
 				const ushort textWidth = metrics.width (label),
 					textHeight = metrics.height (),
@@ -1398,7 +1396,7 @@
 	if (!dlg.exec ())
 		return;
 	
-	QImage* img = new QImage (dlg.fpath ().chars ());
+	QImage* img = new QImage (dlg.fpath ());
 	overlayMeta& info = getOverlay (camera ());
 	
 	if (img->isNull ()) {
--- a/src/gui.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/gui.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -290,10 +290,8 @@
 		delete recent;
 	m_recentFiles.clear ();
 	
-	vector<str> files = io_recentfiles.value / "@";
-	for (long i = files.size() - 1; i >= 0; --i) {
-		str file = files[i];
-		
+	vector<str> files = container_cast<QStringList, vector<str>> (io_recentfiles.value.split ("@"));
+	for (str file : c_rev<str> (files)) {
 		QAction* recent = new QAction (getIcon ("open-recent"), file, this);
 		
 		connect (recent, SIGNAL (triggered ()), this, SLOT (slot_recentFile ()));
@@ -436,11 +434,11 @@
 vector<quickColor> parseQuickColorMeta () {
 	vector<quickColor> meta;
 	
-	for (str colorname : gui_colortoolbar.value / ":") {
+	for (str colorname : gui_colortoolbar.value.split (":")) {
 		if (colorname == "|") {
 			meta << quickColor ({null, null, true});
 		} else {
-			color* col = getColor (atoi (colorname));
+			color* col = getColor (colorname.toLong ());
 			assert (col != null);
 			meta << quickColor ({col, null, false});
 		}
@@ -501,12 +499,12 @@
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 void ForgeWindow::updateTitle () {
-	str title = fmt (APPNAME " %s", fullVersionString().chars ());
+	str title = fmt (APPNAME " %1", fullVersionString());
 	
 	// Append our current file if we have one
 	if (g_curfile) {
-		if (g_curfile->name ().len () > 0)
-			title += fmt (": %s", basename (g_curfile->name ()).c ());
+		if (g_curfile->name ().length () > 0)
+			title += fmt (": %1", basename (g_curfile->name ()));
 		else
 			title += fmt (": <anonymous>");
 		
@@ -515,7 +513,7 @@
 		{
 			// Append title
 			LDComment* comm = static_cast<LDComment*> (g_curfile->obj (0));
-			title += fmt (": %s", comm->text.chars());
+			title += fmt (": %1", comm->text);
 		}
 		
 		if (g_curfile->history ().pos () != g_curfile->savePos ())
@@ -602,11 +600,11 @@
 		
 		switch (obj->getType ()) {
 		case LDObject::Comment:
-			descr = static_cast<LDComment*> (obj)->text.chars();
+			descr = static_cast<LDComment*> (obj)->text;
 			
 			// Remove leading whitespace
-			while (~descr && descr[0] == ' ')
-				descr -= -1;
+			while (descr[0] == ' ')
+				descr.remove (0, 1);
 			break;
 		
 		case LDObject::Empty:
@@ -625,23 +623,22 @@
 			break;
 		
 		case LDObject::Gibberish:
-			descr.format ("ERROR: %s",
-				static_cast<LDGibberish*> (obj)->contents.chars());
+			descr = fmt ("ERROR: %1", static_cast<LDGibberish*> (obj)->contents);
 			break;
 		
 		case LDObject::Vertex:
-			descr.format ("%s", static_cast<LDVertex*> (obj)->pos.stringRep (true).chars());
+			descr = static_cast<LDVertex*> (obj)->pos.stringRep (true);
 			break;
 		
 		case LDObject::Subfile:
 			{
 				LDSubfile* ref = static_cast<LDSubfile*> (obj);
 				
-				descr.format ("%s %s, (", ref->fileInfo ()->name ().chars (),
-					ref->position ().stringRep (true).chars ());
+				descr = fmt ("%1 %2, (", ref->fileInfo ()->name (),
+					ref->position ().stringRep (true));
 				
 				for (short i = 0; i < 9; ++i)
-					descr += fmt ("%s%s", ftoa (ref->transform ()[i]).chars (),
+					descr += fmt ("%1%2", ftoa (ref->transform ()[i]),
 						(i != 8) ? " " : "");
 				
 				descr += ')';
@@ -655,12 +652,12 @@
 		case LDObject::Radial:
 			{
 				LDRadial* rad = static_cast<LDRadial*> (obj);
-				descr.format ("%d / %d %s", rad->segments (), rad->divisions (), rad->radialTypeName ());
+				descr = fmt ("%1 / %2 %3", rad->segments (), rad->divisions (), rad->radialTypeName ());
 				
 				if (rad->type () == LDRadial::Ring || rad->type () == LDRadial::Cone)
-					descr += fmt (" %d", rad->number ());
+					descr += fmt (" %1", rad->number ());
 				
-				descr += fmt (" %s", rad->position ().stringRep (true).chars ());
+				descr += " " + rad->position ().stringRep (true);
 			}
 			break;
 		
@@ -671,11 +668,10 @@
 		
 		// Put it into brackets if it's hidden
 		if (obj->hidden ()) {
-			str copy = descr.chars ();
-			descr.format ("[[ %s ]]", copy.chars ());
+			descr = fmt ("[[ %1 ]]", descr);
 		}
 		
-		QListWidgetItem* item = new QListWidgetItem (descr.chars());
+		QListWidgetItem* item = new QListWidgetItem (descr);
 		item->setIcon (getIcon (g_saObjTypeIcons[obj->getType ()]));
 		
 		// Color gibberish orange on red so it stands out.
@@ -1004,8 +1000,8 @@
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-QPixmap getIcon (const char* iconName) {
-	return (QPixmap (fmt (":/icons/%s.png", iconName)));
+QPixmap getIcon (str iconName) {
+	return (QPixmap (fmt (":/icons/%1.png", iconName)));
 }
 
 // =============================================================================
@@ -1047,7 +1043,7 @@
 			return *meta.qAct;
 	}
 	
-	fatal (fmt ("%s: couldn't find action named `%s'!\n", __func__, name.chars ()));
+	fatal (fmt ("Couldn't find action named `%2'!", name));
 	abort ();
 	return null;
 }
@@ -1061,7 +1057,7 @@
 	QColor col = colinfo->faceColor;
 	if (colinfo->index == maincolor) {
 		// Use the user preferences for main color here
-		col = gl_maincolor.value.chars ();
+		col = gl_maincolor.value;
 		col.setAlpha (gl_maincolor_alpha * 255.0f);
 	}
 	
@@ -1093,8 +1089,8 @@
 		assert (col != null);
 		
 		QIcon ico = makeColorIcon (col, 16);
-		box->addItem (ico, fmt ("[%d] %s (%lu object%s)",
-			pair.first, col->name.chars (), pair.second, plural (pair.second)));
+		box->addItem (ico, fmt ("[%1] %2 (%3 object%4)",
+			pair.first, col->name, pair.second, plural (pair.second)));
 		box->setItemData (row, pair.first);
 		
 		++row;
--- a/src/gui.h	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/gui.h	Fri Jun 14 16:00:54 2013 +0300
@@ -169,7 +169,7 @@
 
 // -----------------------------------------------------------------------------
 // Other GUI-related stuff not directly part of ForgeWindow:
-QPixmap getIcon (const char* sIconName);
+QPixmap getIcon (str iconName);
 vector<quickColor> parseQuickColorMeta ();
 bool confirm (str title, str msg);
 bool confirm (str msg);
--- a/src/gui_actions.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/gui_actions.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -55,7 +55,7 @@
 	
 	str name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)");
 	
-	if (name.len () == 0)
+	if (name.length () == 0)
 		return;
 	
 	closeAll ();
@@ -68,11 +68,11 @@
 void doSave (bool saveAs) {
 	str path = g_curfile->name ();
 	
-	if (~path == 0 || saveAs) {
+	if (path.length () == 0 || saveAs) {
 		path = QFileDialog::getSaveFileName (g_win, "Save As",
 			g_curfile->name (), "LDraw files (*.dat *.ldr)");
 		
-		if (~path == 0) {
+		if (path.length () == 0) {
 			// User didn't give a file name. This happens if the user cancelled
 			// saving in the save file dialog. Abort.
 			return;
@@ -82,14 +82,12 @@
 	if (g_curfile->save (path)) {
 		g_curfile->setName (path);
 		g_win->updateTitle ();
-		
-		logf ("Saved successfully to %s\n", path.chars ());
 	} else {
 		setlocale (LC_ALL, "C");
 		
 		// Tell the user the save failed, and give the option for saving as with it.
 		QMessageBox dlg (QMessageBox::Critical, "Save Failure",
-			fmt ("Failed to save to %s\nReason: %s", path.chars(), strerror (errno)),
+			fmt ("Failed to save to %1\nReason: %2", path, strerror (errno)),
 			QMessageBox::Close, g_win);
 		
 		QPushButton* saveAsBtn = new QPushButton ("Save As");
@@ -301,12 +299,12 @@
 	str fname = QFileDialog::getOpenFileName ();
 	ulong idx = g_win->getInsertionPoint ();
 	
-	if (!~fname)
+	if (!fname.length ())
 		return;
 	
-	FILE* fp = fopen (fname, "r");
+	FILE* fp = fopen (qchars (fname), "r");
 	if (!fp) {
-		critical (fmt ("Couldn't open %s\n%s", fname.chars(), strerror (errno)));
+		critical (fmt ("Couldn't open %1 (%2)", fname, strerror (errno)));
 		return;
 	}
 	
@@ -333,18 +331,19 @@
 		return;
 	
 	str fname = QFileDialog::getSaveFileName ();
-	if (fname.len () == 0)
+	if (fname.length () == 0)
 		return;
 	
 	QFile file (fname);
 	if (!file.open (QIODevice::WriteOnly | QIODevice::Text)) {
-		critical (fmt ("Unable to open %s for writing (%s)", fname.chars (), strerror (errno)));
+		critical (fmt ("Unable to open %1 for writing (%2)", fname, strerror (errno)));
 		return;
 	}
 	
 	for (LDObject* obj : g_win->sel ()) {
 		str contents = obj->raw ();
-		file.write (contents, contents.len ());
+		QByteArray data = contents.toUtf8 ();
+		file.write (data, data.size ());
 		file.write ("\r\n", 2);
 	}
 }
@@ -393,15 +392,15 @@
 	QImage img = imageFromScreencap (imgdata, w, h);
 	
 	str root = basename (g_curfile->name ());
-	if (~root >= 4 && root.substr (~root - 4, -1) == ".dat")
-		root -= 4;
+	if (root.right (4) == ".dat")
+		root.chop (4);
 	
-	str defaultname = (~root > 0) ? fmt ("%s.png", root.c ()) : "";
+	str defaultname = (root.length () > 0) ? fmt ("%1.png", root) : "";
 	str fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname,
 		"PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)");
 	
-	if (~fname > 0 && !img.save (fname))
-		critical (fmt ("Couldn't open %s for writing to save screencap: %s", fname.c (), strerror (errno)));
+	if (fname.length () > 0 && !img.save (fname))
+		critical (fmt ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno)));
 	
 	delete[] imgdata;
 }
@@ -452,7 +451,7 @@
 	
 	bool ok;
 	double depth = QInputDialog::getDouble (g_win, "Set Draw Depth",
-		fmt ("Depth value for %s Camera:", g_win->R ()->cameraName ()),
+		fmt ("Depth value for %1 Camera:", g_win->R ()->cameraName ()),
 		g_win->R ()->depthValue (), -10000.0f, 10000.0f, 3, &ok);
 	
 	if (ok)
--- a/src/gui_editactions.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/gui_editactions.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -173,7 +173,7 @@
 		str errmsg = fmt ("Couldn't replace %lu radials as replacement subfiles could not be loaded:<br />", (ulong)fails.size ());
 		
 		for (str& fail : fails) 
-			errmsg += fmt ("* %s<br />", fail.chars ());
+			errmsg += fmt ("* %1<br />", fail);
 		
 		critical (errmsg);
 	}
@@ -516,8 +516,12 @@
 	for (short i = 0; i < obj->vertices (); ++i) {
 		vertex v = obj->getVertex (i);
 		
-		for (const Axis ax : g_Axes)
-			v[ax] = atof (fmt ("%.3f", v[ax]));
+		for (const Axis ax : g_Axes) {
+			// HACK: :p
+			char valstr[64];
+			sprintf (valstr, "%.3f", v[ax]);
+			v[ax] = atof (valstr);
+		}
 		
 		obj->setVertex (i, v);
 	}
--- a/src/ldtypes.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/ldtypes.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -72,11 +72,11 @@
 
 // =============================================================================
 str LDComment::raw () {
-	return fmt ("0 %s", text.chars ());
+	return fmt ("0 %1", text);
 }
 
 str LDSubfile::raw () {
-	str val = fmt ("1 %d %s ", color (), position ().stringRep (false).chars ());
+	str val = fmt ("1 %1 %2 ", color (), position ());
 	val += transform ().stringRep ();
 	val += ' ';
 	val += fileInfo ()->name ();
@@ -84,38 +84,38 @@
 }
 
 str LDLine::raw () {
-	str val = fmt ("2 %d", color ());
+	str val = fmt ("2 %1", color ());
 	
 	for (ushort i = 0; i < 2; ++i)
-		val += fmt  (" %s", getVertex (i).stringRep (false).chars ());
+		val += fmt (" %1", getVertex (i));
 	
 	return val;
 }
 
 str LDTriangle::raw () {
-	str val = fmt ("3 %d", color ());
+	str val = fmt ("3 %1", color ());
 	
 	for (ushort i = 0; i < 3; ++i)
-		val += fmt  (" %s", getVertex (i).stringRep (false).chars ());
+		val += fmt  (" %1", getVertex (i));
 	
 	return val;
 }
 
 str LDQuad::raw () {
-	str val = fmt ("4 %d", color ());
+	str val = fmt ("4 %1", color ());
 	
 	for (ushort i = 0; i < 4; ++i)
-		val += fmt  (" %s", getVertex (i).stringRep (false).chars ());
+		val += fmt  (" %1", getVertex (i));
 	
 	return val;
 }
 
 str LDCondLine::raw () {
-	str val = fmt ("5 %d", color ());
+	str val = fmt ("5 %1", color ());
 	
 	// Add the coordinates
 	for (ushort i = 0; i < 4; ++i)
-		val += fmt  (" %s", getVertex (i).stringRep (false).chars ());
+		val += fmt  (" %1", getVertex (i));
 	
 	return val;
 }
@@ -125,7 +125,7 @@
 }
 
 str LDVertex::raw () {
-	return fmt ("0 !LDFORGE VERTEX %d %s", color (), pos.stringRep (false).chars());
+	return fmt ("0 !LDFORGE VERTEX %1 %2", color (), pos);
 }
 
 str LDEmpty::raw () {
@@ -145,7 +145,7 @@
 };
 
 str LDBFC::raw () {
-	return fmt ("0 BFC %s", LDBFC::statements[type]);
+	return fmt ("0 BFC %1", LDBFC::statements[type]);
 }
 
 // =============================================================================
@@ -394,13 +394,13 @@
 		if (!firstDetails)
 			text += ", ";
 		
-		str noun = fmt ("%s%s", g_saObjTypeNames[objType], plural (objCount));
+		str noun = fmt ("%1%2", g_saObjTypeNames[objType], plural (objCount));
 		
 		// Plural of "vertex" is "vertices". Stupid English.
 		if (objType == LDObject::Vertex && objCount != 1)
 			noun = "vertices";
 		
-		text += fmt  ("%lu %s", objCount, noun.chars ());
+		text += fmt  ("%1 %2", objCount, noun);
 		firstDetails = false;
 	}
 	
@@ -645,10 +645,10 @@
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 str LDRadial::raw () {
-	return fmt ("0 !LDFORGE RADIAL %s %d %d %d %d %s %s",
-		str (radialTypeName ()).upper ().strip (' ').c (),
+	return fmt ("0 !LDFORGE RADIAL %1 %2 %3 %4 %5 %6 %7",
+		str (radialTypeName ()).toUpper ().remove (' '),
 		color (), segments (), divisions (), number (),
-		position ().stringRep (false).chars (), transform ().stringRep().chars ());
+		position ().stringRep (false), transform ().stringRep());
 }
 
 char const* g_radialNameRoots[] = {
@@ -679,18 +679,18 @@
 	}
 	
 	// Compose some general information: prefix, fraction, root, ring number
-	str prefix = (divisions () == lores) ? "" : fmt ("%d/", divisions ());
-	str frac = fmt ("%d-%d", numer, denom);
+	str prefix = (divisions () == lores) ? "" : fmt ("%1/", divisions ());
+	str frac = fmt ("%1-%2", numer, denom);
 	str root = g_radialNameRoots[type ()];
-	str num = (type () == Ring || type () == Cone) ? fmt ("%d", number ()) : "";
+	str num = (type () == Ring || type () == Cone) ? fmt ("%1", number ()) : "";
 	
 	// Truncate the root if necessary (7-16rin4.dat for instance).
 	// However, always keep the root at least 2 characters.
-	short extra = (~frac + ~num + ~root) - 8;
-	root -= min<short> (max<short> (extra, 0), 2);
+	short extra = (frac.length () + num.length () + root.length ()) - 8;
+	root.chop (min<short> (max<short> (extra, 0), 2));
 	
 	// Stick them all together and return the result.
-	return fmt ("%s%s%s%s.dat", prefix.chars(), frac.chars (), root.chars (), num.chars ());
+	return (prefix + frac + root + num + ".dat");
 }
 
 // =============================================================================
--- a/src/main.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/main.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -19,6 +19,7 @@
 #include <QApplication>
 #include <QMessageBox>
 #include <QAbstractButton>
+#include <qfile.h>
 #include "gui.h"
 #include "file.h"
 #include "bbox.h"
@@ -32,14 +33,33 @@
 ForgeWindow* g_win = null; 
 bbox g_BBox;
 const QApplication* g_app = null;
+QFile g_file_stdout, g_file_stderr;
 
 const vertex g_origin (0.0f, 0.0f, 0.0f);
 const matrix g_identity ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
 
+void doPrint (QFile& f, initlist<StringFormatArg> args) {
+	str msg = DoFormat (args);
+	f.write (msg.toUtf8 (), msg.length ());
+	f.flush ();
+}
+
+void doPrint (FILE* fp, initlist<StringFormatArg> args) {
+	if (fp == stdout)
+		doPrint (g_file_stdout, args);
+	else if (fp == stderr)
+		doPrint (g_file_stderr, args);
+	
+	fatal ("unknown FILE* argument");
+}
+
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 int main (int argc, char* argv[]) {
+	g_file_stdout.open (stdout, QIODevice::WriteOnly);
+	g_file_stderr.open (stderr, QIODevice::WriteOnly);
+	
 	const QApplication app (argc, argv);
 	g_app = &app;
 	g_curfile = NULL;
@@ -94,9 +114,9 @@
 
 str versionString () {
 #if VERSION_PATCH == 0
-	return fmt ("%d.%d", VERSION_MAJOR, VERSION_MINOR);
+	return fmt ("%1.%2", VERSION_MAJOR, VERSION_MINOR);
 #else
-	return fmt ("%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
+	return fmt ("%1.%2.%3", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
 #endif // VERSION_PATCH
 }
 
@@ -115,7 +135,7 @@
 }
 
 str fullVersionString () {
-	return fmt ("v%s%s", versionString ().chars (), versionMoniker ());
+	return fmt ("v%1%2", versionString (), versionMoniker ());
 }
 
 static void bombBox (str msg) {
@@ -134,7 +154,7 @@
 }
 
 void assertionFailure (const char* file, const ulong line, const char* funcname, const char* expr) {
-	str errmsg = fmt ("File %s\nLine: %lu:\nFunction %s:\n\nAssertion `%s' failed",
+	str errmsg = fmt ("File: %1\nLine: %2:\nFunction %3:\n\nAssertion `%4' failed",
 		file, line, funcname, expr);
 	
 #if BUILD_ID == BUILD_INTERNAL
@@ -143,7 +163,7 @@
 	errmsg += ".";
 #endif
 	
-	printf ("%s\n", errmsg.chars ());
+	printf ("%s\n", qchars (errmsg));
 	
 #if BUILD_ID == BUILD_INTERNAL
 	if (g_win)
@@ -155,10 +175,10 @@
 }
 
 void fatalError (const char* file, const ulong line, const char* funcname, str msg) {
-	str errmsg = fmt ("Aborting over a call to fatal():\nFile: %s\nLine: %lu\nFunction: %s\n\n%s",
-		file, line, funcname, msg.chars ());
+	str errmsg = fmt ("Aborting over a call to fatal():\nFile: %1\nLine: %2\nFunction: %3\n\n%4",
+		file, line, funcname, msg);
 	
-	printf ("%s\n", errmsg.chars ());
+	printf ("%s\n", qchars (errmsg));
 	
 	if (g_win)
 		g_win->deleteLater ();
--- a/src/misc.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/misc.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -137,16 +137,17 @@
 	// turn into anything weird (like commas)
 	setlocale (LC_NUMERIC, "C");
 	
-	str rep = fmt ("%f", num);
+	str rep;
+	rep.sprintf ("%f", num);
 	
 	// Remove trailing zeroes
-	while (rep[~rep - 1] == '0')
-		rep -= 1;
+	while (rep.right (1) == "0")
+		rep.chop (1);
 	
-	// If there was only zeroes in the decimal place, remove
+	// If there were only zeroes in the decimal place, remove
 	// the decimal point now.
-	if (rep[~rep - 1] == '.')
-		rep -= 1;
+	if (rep.right (1) == ".")
+		rep.chop (1);
 	
 	return rep;
 }
@@ -157,9 +158,11 @@
 bool isNumber (const str& tok) {
 	bool gotDot = false;
 	
-	for (const char& c : tok) {
+	for (int i = 0; i < tok.length (); ++i) {
+		const qchar c = tok[i];
+		
 		// Allow leading hyphen for negatives
-		if (&c == &tok[0] && c == '-')
+		if (i == 0 && c == '-')
 			continue;
 		
 		// Check for decimal point
@@ -238,11 +241,20 @@
 	edit_rotpoint_z = pos[Z];
 }
 
+str join (initlist<StringFormatArg> vals, str delim) {
+	QStringList list;
+	for (const StringFormatArg& arg : vals)
+		list << arg.value ();
+	
+	return list.join (delim);
+}
+
+
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 StringParser::StringParser (str inText, char sep) {
-	m_tokens = inText.split (sep);
+	m_tokens = container_cast<QStringList, vector<str>> (inText.split (sep, QString::SkipEmptyParts));
 	m_pos = -1;
 }
 
--- a/src/misc.h	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/misc.h	Fri Jun 14 16:00:54 2013 +0300
@@ -21,6 +21,7 @@
 
 #include "config.h"
 #include "common.h"
+#include "types.h"
 
 #define NUM_PRIMES 500
 
@@ -39,6 +40,8 @@
 // Simplifies the given fraction.
 void simplify (short& numer, short& denom);
 
+str join (initlist<StringFormatArg> vals, str delim = " ");
+
 // Grid stuff
 typedef struct {
 	const char* const name;
@@ -58,6 +61,15 @@
 vertex rotPoint (const vector<LDObject*>& objs);
 void configRotationPoint ();
 
+template<class T, class R> R container_cast (const T& a) {
+	R b;
+	
+	for (auto i : a)
+		b << i;
+	
+	return b;
+}
+
 // =============================================================================
 namespace Grid {
 	enum Type {
--- a/src/string.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/string.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -1,434 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 Santeri Piippo
- *  
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdexcept>
-#include "common.h"
-#include "string.h"
-
-String::String () {}
-
-String::String (const char* data) {
-	m_string = data;
-}
-
-String::String (const QString data) {
-	m_string = data.toStdString ();
-}
-
-String::String (std::string data) {
-	m_string = data;
-}
-
-str fmt (const char* fmtstr, ...) {
-	va_list va;
-	
-	va_start (va, fmtstr);
-	char* buf = dynafmt (fmtstr, va, 256);
-	va_end (va);
-	
-	str msg = buf;
-	delete[] buf;
-	return msg;
-}
-
-char* dynafmt (const char* fmtstr, va_list va, ulong size) {
-	char* buf = null;
-	ushort run = 0;
-	
-	do {
-		try {
-			buf = new char[size];
-		} catch (std::bad_alloc&) {
-			// fmt uses dynafmt, so using fmt here is dangerous and could lead
-			// into infinite recursion. Thus, use a C string this one time.
-			char err[256];
-			sprintf (err, "caught std::bad_alloc on run #%u while trying to allocate %lu bytes", run + 1, size);
-			fatal (err);
-		}
-		
-		if (!vsnprintf (buf, size - 1, fmtstr, va)) {
-			delete[] buf;
-			buf = null;
-		}
-		
-		size *= 2;
-		++run;
-	} while (buf == null);
-	
-	return buf;
-}
-
-void String::append (const char* data) {
-	m_string.append (data);
-}
-
-void String::append (const char data) {
-	m_string.push_back (data);
-}
-
-void String::append (const String data) {
-	m_string.append (data.chars ());
-}
-
-String::it String::begin () {
-	return m_string.begin ();
-}
-
-String::c_it String::begin () const {
-	return m_string.cbegin ();
-}
-
-const char* String::c () const {
-	return chars ();
-}
-
-size_t String::capacity () const {
-	return m_string.capacity ();
-}
-
-const char* String::chars () const {
-	return m_string.c_str ();
-}
-
-int String::compare (const char* other) const {
-	return m_string.compare (other);
-}
-
-int String::compare (String other) const {
-	return m_string.compare (other);
-}
-
-String::it String::end () {
-	return m_string.end ();
-}
-
-String::c_it String::end () const {
-	return m_string.end ();
-}
-
-void String::clear () {
-	m_string.clear ();
-}
-
-bool String::empty() const {
-	return m_string.empty ();
-}
-
-void String::erase (size_t pos) {
-	m_string.erase (m_string.begin () + pos);
-}
-
-void String::insert (size_t pos, char c) {
-	m_string.insert (m_string.begin () + pos, c);
-}
-
-size_t String::len () const {
-	return m_string.length ();
-}
-
-size_t String::maxSize () const {
-	return m_string.max_size ();
-}
-
-void String::resize (size_t n) {
-	m_string.resize (n);
-}
-
-void String::shrinkToFit () {
-	m_string.shrink_to_fit ();
-}
-
-
-
-void String::trim (short n) {
-	if (n > 0)
-		for (short i = 0; i < n; ++i)
-			m_string.erase (m_string.end () - 1 - i);
-	else
-		for (short i = abs (n) - 1; i >= 0; ++i)
-			m_string.erase (m_string.begin () + i);
-}
-
-String String::strip (char unwanted) {
-	return strip ({unwanted});
-}
-
-String String::strip (std::initializer_list<char> unwanted) {
-	String copy (m_string);
-	
-	for (char c : unwanted) {
-		for (long i = len (); i >= 0; --i)
-			if (copy[(size_t) i] == c)
-				copy.erase (i);
-	}
-	
-	return copy;
-}
-
-String String::upper() const {
-	String newstr = m_string;
-	
-	for (char& c : newstr)
-		if (c >= 'a' && c <= 'z')
-			c -= 'a' - 'A';
-	
-	return newstr;
-}
-
-String String::lower () const {
-	String newstr = m_string;
-
-	for (char& c : newstr)
-		if (c >= 'A' && c <= 'Z')
-			c += 'a' - 'A';
-	
-	return newstr;
-}
-
-vector<String> String::split (char del) const {
-	String delimstr;
-	delimstr += del;
-	return split (delimstr);
-}
-
-vector<String> String::split (String del) const {
-	vector<String> res;
-	size_t a = 0;
-	
-	// Find all separators and store the text left to them.
-	while (1) {
-		long b = first (del, a);
-		
-		if (b == -1)
-			break;
-		
-		String sub = substr (a, b);
-		if (~sub > 0)
-			res.push_back (substr (a, b));
-		
-		a = b + strlen (del);
-	}
-	
-	// Add the string at the right of the last separator
-	if (a < len ())
-		res.push_back (substr (a, len()));
-	
-	return res;
-}
-
-void String::replace (const char* a, const char* b) {
-	long pos;
-	
-	while ((pos = first (a)) != -1)
-		m_string = m_string.replace (pos, strlen (a), b);
-}
-
-void String::format (const char* fmtstr, ...) {
-	va_list va;
-	
-	va_start (va, fmtstr);
-	char* buf = dynafmt (fmtstr, va, 256);
-	va_end (va);
-	
-	m_string = buf;
-	delete[] buf;
-}
-
-ushort String::count (const char needle) const {
-	ushort numNeedles = 0;
-	
-	for (const char& c : m_string)
-		if (c == needle)
-			numNeedles++;
-	
-	return numNeedles;
-}
-
-String String::substr (long a, long b) const {
-	if (b == -1)
-		b = len ();
-	
-	str sub;
-	
-	try {
-		sub = m_string.substr (a, b - a);
-	} catch (const std::out_of_range& e) {
-		fatal (fmt ("caught std::out_of_range, coords were: (%ld, %ld), string: `%s', length: %lu",
-			a, b, chars (), (ulong) len ()));
-	}
-	
-	return sub;
-}
-
-// ============================================================================
-int String::first (const char* c, int a) const {
-	unsigned int r = 0;
-	unsigned int index = 0;
-	for (; a < (int) len (); a++) {
-		if (m_string[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 String::last (const char* c, int a) const {
-	if (a == -1)
-		a = len();
-	
-	int max = strlen (c) - 1;
-	
-	int r = max;
-	for (; a >= 0; a--) {
-		if (m_string[a] == c[r]) {
-			r--;
-			if (r == -1)
-				return a;
-		} else {
-			if (r != max)
-				a++;
-			
-			r = max;
-		}
-	}
-	
-	return -1;
-}
-
-String String::operator+ (const String data) const {
-	String newstr = *this;
-	newstr += data;
-	return newstr;
-}
-
-String String::operator+ (const char* data) const {
-	String newstr = *this;
-	newstr += data;
-	return newstr;
-}
-
-String& String::operator+= (const String data) {
-	append (data);
-	return *this;
-}
-
-String& String::operator+= (const char* data) {
-	append (data);
-	return *this;
-}
-
-String& String::operator+= (const char data) {
-	append (data);
-	return *this;
-}
-
-String String::operator+ () const {
-	return upper ();
-}
-
-String String::operator- () const {
-	return lower ();
-}
-
-String String::operator- (size_t n) const {
-	String newstr = m_string;
-	newstr -= n;
-	return newstr;
-}
-
-String& String::operator-= (size_t n) {
-	trim (n);
-	return *this;
-}
-
-size_t String::operator~ () const {
-	return len ();
-}
-
-vector<String> String::operator/ (String del) const {
-	return split (del);
-}
-
-char& String::operator[] (size_t n) {
-	return m_string[n];
-}
-
-const char& String::operator[] (size_t n) const {
-	return m_string[n];
-}
-
-bool String::operator== (const String other) const {
-	return compare (other) == 0;
-}
-
-bool String::operator== (const char* other) const {
-	return compare (other) == 0;
-}
-
-bool String::operator!= (const String other) const {
-	return compare (other) != 0;
-}
-
-bool String::operator!= (const char* other) const {
-	return compare (other) != 0;
-}
-
-bool String::operator! () const {
-	return empty ();
-}
-
-String::operator const char* () const {
-	return chars ();
-}
-
-String::operator QString () {
-	return chars ();
-}
-
-String::operator const QString () const {
-	return chars ();
-}
-
-str String::join (const vector<str>& items, const str& delim) {
-	str text;
-	
-	for (const str& item : items) {
-		if (item != items[0])
-			text += delim;
-		
-		text += item;
-	}
-	
-	return text;
-}
\ No newline at end of file
--- a/src/string.h	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/string.h	Fri Jun 14 16:00:54 2013 +0300
@@ -24,81 +24,5 @@
 #include <QString>
 #include "types.h"
 
-typedef class String {
-public:
-	typedef typename std::string::iterator it;
-	typedef typename std::string::const_iterator c_it;
-	typedef vector<String> stringlist;
-	
-	String ();
-	String (const char* data);
-	String (const QString data);
-	String (std::string data);
-	
-	void           append         (const char* data);
-	void           append         (const char data);
-	void           append         (const String data);
-	it             begin          ();
-	c_it           begin          () const;
-	const char*    c              () const;
-	size_t         capacity       () const;
-	const char*    chars          () const;
-	int            compare        (const char* other) const;
-	int            compare        (String other) const;
-	it             end            ();
-	c_it           end            () const;
-	void           clear          ();
-	ushort         count          (const char needle) const;
-	bool           empty          () const;
-	void           erase          (size_t pos);
-	int            first          (const char* c, int a = 0) const;
-	void           format         (const char* fmtstr, ...);
-	void           insert         (size_t pos, char c);
-	int            last           (const char* c, int a = -1) const;
-	size_t         len            () const;
-	String         lower          () const;
-	size_t         maxSize        () const;
-	void           replace        (const char* a, const char* b);
-	void           resize         (size_t n);
-	void           shrinkToFit    ();
-	stringlist     split          (String del) const;
-	stringlist     split          (char del) const;
-	String         strip          (char unwanted);
-	String         strip          (std::initializer_list<char> unwanted);
-	String         substr         (long a, long b) const;
-	void           trim           (short n);
-	String         upper          () const;
-	
-	String         operator+      (const String data) const;
-	String         operator+      (const char* data) const;
-	String&        operator+=     (const String data);
-	String&        operator+=     (const char* data);
-	String&        operator+=     (const char data);
-	String         operator+      () const;
-	String         operator-      () const;
-	String         operator-      (size_t n) const;
-	String&        operator-=     (size_t n);
-	size_t         operator~      () const;
-	vector<String> operator/      (String del) const;
-	char&          operator[]     (size_t n);
-	const char&    operator[]     (size_t n) const;
-	bool           operator==     (const String other) const;
-	bool           operator==     (const char* other) const;
-	bool           operator!=     (const String other) const;
-	bool           operator!=     (const char* other) const;
-	bool           operator!      () const;
-	operator const char*          () const;
-	operator QString              ();
-	operator const QString        () const;
-	
-	static str     join           (const vector<str>& items, const str& delim);
-	
-private:
-	std::string m_string;
-} str;
-
-// Accessories
-char*          dynafmt        (const char* fmtstr, va_list va, ulong size);
-str            fmt            (const char* fmtstr, ...);
 
 #endif // STR_H
\ No newline at end of file
--- a/src/types.cpp	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/types.cpp	Fri Jun 14 16:00:54 2013 +0300
@@ -16,11 +16,23 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <QObject>
+#include <QStringList>
 #include <assert.h>
 #include "common.h"
 #include "types.h"
 #include "misc.h"
 
+str DoFormat (vector<StringFormatArg> args) {
+	assert (args.size () >= 1);
+	str text = args[0].value ();
+	
+	for (uchar i = 1; i < args.size (); ++i)
+		text = text.arg (args[i].value ());
+	
+	return text;
+}
+
 vertex::vertex (double x, double y, double z) {
 	m_coords[X] = x;
 	m_coords[Y] = y;
@@ -45,10 +57,14 @@
 
 // =============================================================================
 str vertex::stringRep (bool mangled) const {
-	return fmt (mangled ? "(%s, %s, %s)" : "%s %s %s",
-		ftoa (coord (X)).chars(),
-		ftoa (coord (Y)).chars(),
-		ftoa (coord (Z)).chars());
+	if (mangled)
+		return (str ("(") +
+		ftoa (coord (X)) + ", " +
+		ftoa (coord (Y)) + ", " +
+		ftoa (coord (Z)) + ")");
+	
+	return QStringList ({ftoa (coord (X)), ftoa (coord (Y)),
+		ftoa (coord (Z))}).join (" ");
 }
 
 // =============================================================================
@@ -165,7 +181,7 @@
 		if (i > 0)
 			val += ' ';
 		
-		val += fmt ("%s", ftoa (m_vals[i]).chars());
+		val += ftoa (m_vals[i]);
 	}
 	
 	return val;	
@@ -204,4 +220,53 @@
 		(val (2) * val (4) * val (6)) -
 		(val (1) * val (3) * val (8)) -
 		(val (0) * val (5) * val (7));
+}
+
+// =============================================================================
+StringFormatArg::StringFormatArg (const str& v) {
+	m_val = v;
+}
+
+StringFormatArg::StringFormatArg (const char& v) {
+	m_val = v;
+}
+
+StringFormatArg::StringFormatArg (const uchar& v) {
+	m_val = v;
+}
+
+StringFormatArg::StringFormatArg (const qchar& v) {
+	m_val = v;
+}
+
+StringFormatArg::StringFormatArg (const float& v) {
+	m_val = ftoa (v);
+}
+
+StringFormatArg::StringFormatArg (const double& v) {
+	m_val = ftoa (v);
+}
+
+StringFormatArg::StringFormatArg (const vertex& v) {
+	m_val = v.stringRep (false);
+}
+
+StringFormatArg::StringFormatArg (const matrix& v) {
+	m_val = v.stringRep ();
+}
+
+StringFormatArg::StringFormatArg (const char* v) {
+	m_val = v;
+}
+
+StringFormatArg::StringFormatArg (const strconfig& v) {
+	m_val = v.value;
+}
+
+StringFormatArg::StringFormatArg (const intconfig& v) {
+	m_val.number (v.value);
+}
+
+StringFormatArg::StringFormatArg (const floatconfig& v) {
+	m_val.number (v.value);
 }
\ No newline at end of file
--- a/src/types.h	Thu Jun 13 16:33:17 2013 +0300
+++ b/src/types.h	Fri Jun 14 16:00:54 2013 +0300
@@ -19,13 +19,17 @@
 #ifndef TYPES_H
 #define TYPES_H
 
+#include <QString>
 #include <vector>
 #include "common.h"
 
-class String;
-typedef String str;
+typedef QChar qchar;
+typedef QString str;
 template<class T> class ConstVectorReverser;
 template<class T> using c_rev = ConstVectorReverser<T>;
+class strconfig;
+class intconfig;
+class floatconfig;
 
 typedef unsigned int uint;
 typedef unsigned short ushort;
@@ -316,4 +320,47 @@
 template<class T> using rev = VectorReverser<T>;
 template<class T> using c_rev = ConstVectorReverser<T>;
 
+// =============================================================================
+class StringFormatArg {
+public:
+	StringFormatArg (const str& v);
+	StringFormatArg (const char& v);
+	StringFormatArg (const uchar& v);
+	StringFormatArg (const qchar& v);
+	
+#define NUMERIC_FORMAT_ARG(T,C) \
+	StringFormatArg (const T& v) { \
+		char valstr[32]; \
+		sprintf (valstr, "%" #C, v); \
+		m_val = valstr; \
+	}
+	
+	NUMERIC_FORMAT_ARG (int, d)
+	NUMERIC_FORMAT_ARG (short, d)
+	NUMERIC_FORMAT_ARG (long, ld)
+	NUMERIC_FORMAT_ARG (uint, u)
+	NUMERIC_FORMAT_ARG (ushort, u)
+	NUMERIC_FORMAT_ARG (ulong, lu)
+	
+	StringFormatArg (const float& v);
+	StringFormatArg (const double& v);
+	StringFormatArg (const vertex& v);
+	StringFormatArg (const matrix& v);
+	StringFormatArg (const char* v);
+	StringFormatArg (const strconfig& v);
+	StringFormatArg (const intconfig& v);
+	StringFormatArg (const floatconfig& v);
+	
+	str value () const { return m_val; }
+private:
+	str m_val;
+};
+
+str DoFormat (vector< StringFormatArg > args);
+#ifndef IN_IDE_PARSER
+#define fmt(...) DoFormat ({__VA_ARGS__})
+#else
+str fmt (const char* fmtstr, ...);
+#endif
+
 #endif // TYPES_H
\ No newline at end of file

mercurial