src/file.cpp

changeset 309
11ec6aa1f1fb
parent 308
4e2425bd4dc7
child 310
c62edce5668c
--- a/src/file.cpp	Mon Jul 01 17:27:37 2013 +0300
+++ b/src/file.cpp	Mon Jul 01 19:48:29 2013 +0300
@@ -33,97 +33,107 @@
 #include "dialogs.h"
 #include "gldraw.h"
 
-cfg (str, io_ldpath, "");
-cfg (str, io_recentfiles, "");
+cfg( str, io_ldpath, "" );
+cfg( str, io_recentfiles, "" );
 
 static bool g_loadingMainFile = false;
+static const int g_MaxRecentFiles = 5;
 
 // =============================================================================
-namespace LDPaths {
+namespace LDPaths
+{
 	static str pathError;
 	
-	struct {
+	struct
+	{
 		str LDConfigPath;
 		str partsPath, primsPath;
 	} pathInfo;
 	
-	void initPaths () {
-		if (!tryConfigure (io_ldpath)) {
+	void initPaths ()
+	{
+		if( !tryConfigure ( io_ldpath ))
+		{
 			LDrawPathDialog dlg (false);
 			
-			if (!dlg.exec ())
-				exit (0);
+			if( !dlg.exec () )
+				exit( 0 );
 			
-			io_ldpath = dlg.filename ();
+			io_ldpath = dlg.filename();
 		}
 	}
 	
-	bool tryConfigure (str path) {
+	bool tryConfigure( str path )
+	{
 		QDir dir;
 		
-		if (!dir.cd (path)) {
+		if( !dir.cd( path ))
+		{
 			pathError = "Directory does not exist.";
 			return false;
 		}
 		
-		QStringList mustHave = {"LDConfig.ldr", "parts", "p"};
-		QStringList contents = dir.entryList (mustHave);
+		QStringList mustHave = { "LDConfig.ldr", "parts", "p" };
+		QStringList contents = dir.entryList( mustHave );
 		
 		if (contents.size () != mustHave.size ()) {
 			pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/.";
 			return false;
 		}
 		
-		pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path);
-		pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path);
-		pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path);
+		pathInfo.partsPath = fmt( "%1" DIRSLASH "parts", path );
+		pathInfo.LDConfigPath = fmt( "%1" DIRSLASH "LDConfig.ldr", path );
+		pathInfo.primsPath = fmt( "%1" DIRSLASH "p", path );
 		
 		return true;
 	}
 	
 	// Accessors
-	str getError () { return pathError; }
-	str ldconfig () { return pathInfo.LDConfigPath; }
-	str prims () { return pathInfo.primsPath; }
-	str parts () { return pathInfo.partsPath; }
+	str getError() { return pathError; }
+	str ldconfig() { return pathInfo.LDConfigPath; }
+	str prims() { return pathInfo.primsPath; }
+	str parts() { return pathInfo.partsPath; }
 }
 
 // =============================================================================
-LDOpenFile::LDOpenFile () {
-	setImplicit (true);
-	setSavePos (-1);
-	m_history.setFile (this);
+LDOpenFile::LDOpenFile()
+{
+	setImplicit( true );
+	setSavePos( -1 );
+	m_history.setFile( this );
 }
 
 // =============================================================================
-LDOpenFile::~LDOpenFile () {
+LDOpenFile::~LDOpenFile()
+{
 	// Clear everything from the model
-	for (LDObject* obj : m_objs)
+	for( LDObject* obj : m_objs )
 		delete obj;
 	
 	// Clear the cache as well
-	for (LDObject* obj : m_cache)
+	for( LDObject* obj : m_cache )
 		delete obj;
 }
 
 // =============================================================================
-LDOpenFile* findLoadedFile (str name) {
-	for (LDOpenFile* file : g_loadedFiles)
-		if (file->name () == name)
+LDOpenFile* findLoadedFile( str name )
+{
+	for( LDOpenFile* file : g_loadedFiles )
+		if( file->name () == name )
 			return file;
 	
 	return null;
 }
 
 // =============================================================================
-str dirname (str path) {
-	long lastpos = path.lastIndexOf (DIRSLASH);
+str dirname( str path ) {
+	long lastpos = path.lastIndexOf( DIRSLASH );
 	
-	if (lastpos > 0)
-		return path.left (lastpos);
+	if( lastpos > 0 )
+		return path.left( lastpos );
 	
 #ifndef _WIN32
-	if (path[0] == DIRSLASH_CHAR)
+	if( path[0] == DIRSLASH_CHAR )
 		return DIRSLASH;
 #endif // _WIN32
 	
@@ -132,56 +142,62 @@
 
 // =============================================================================
 str basename (str path) {
-	long lastpos = path.lastIndexOf (DIRSLASH);
+	long lastpos = path.lastIndexOf( DIRSLASH );
 	
-	if (lastpos != -1)
-		return path.mid (lastpos + 1);
+	if( lastpos != -1 )
+		return path.mid( lastpos + 1 );
 	
 	return path;
 }
 
 // =============================================================================
 File* openLDrawFile (str relpath, bool subdirs) {
-	print ("%1: Try to open %2\n", __func__, relpath);
+	print( "%1: Try to open %2\n", __func__, relpath );
 	File* f = new File;
 	
+	// LDraw models use Windows-style path separators. If we're not on Windows,
+	// replace the path separator now before opening any files.
 #ifndef WIN32
-	relpath.replace ("\\", "/");
+	relpath.replace( "\\", "/" );
 #endif // WIN32
 	
-	if (g_curfile != null) {
+	if( g_curfile )
+	{
+		// First, try find the file in the current model's file path. We want a file
+		// in the immediate vicinity of the current model to override stock LDraw stuff.
 		str partpath = fmt ("%1" DIRSLASH "%2", dirname (g_curfile->name ()), relpath);
-		print ("try %1\n", partpath);
 		
 		if (f->open (partpath, File::Read))
 			return f;
 	}
 	
-	print ("try %1\n", relpath);
 	if (f->open (relpath, File::Read))
 		return f;
 	
 	str fullPath;
-	if (io_ldpath.value.length () > 0) {
+	if( io_ldpath.value.length() > 0 )
+	{
 		// Try with just the LDraw path first
 		fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath);
-		print ("try %1\n", fullPath);
 		
-		if (f->open (fullPath, File::Read))
+		if( f->open( fullPath, File::Read ))
 			return f;
 		
-		if (subdirs) {
-			for (auto subdir : initlist<const char*> ({"parts", "p"})) {
+		if( subdirs )
+		{
+			// Look in sub-directories: parts and p
+			for( auto subdir : initlist<const str>({ "parts", "p" }))
+			{
 				fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3",
 					io_ldpath, subdir, relpath);
 				
-				printf ("try %s\n", qchars (fullPath));
 				if (f->open (fullPath, File::Read))
 					return f;
 			}
 		}
 	}
 	
+	// Did not find the file.
 	delete f;
 	return null;
 }
@@ -189,45 +205,49 @@
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-void FileLoader::work () {
+void FileLoader::work()
+{
 	m_progress = 0;
 	abortflag = false;
 	
-	for (str line : *PROP_NAME (file)) {
+	for( str line : *PROP_NAME( file )) {
 		// Trim the trailing newline
 		qchar c;
-		while ((c = line[line.length () - 1]) == '\n' || c == '\r')
-			line.chop (1);
+		while(( c = line[line.length () - 1] ) == '\n' || c == '\r' )
+			line.chop( 1 );
 		
-		LDObject* obj = parseLine (line);
-		assert (obj != null);
+		LDObject* obj = parseLine( line );
 		
 		// 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, qchars (static_cast<LDGibberish*> (obj)->reason));
+		if( obj->getType () == LDObject::Gibberish )
+		{
+/*
+			logf( LOG_Warning, "Couldn't parse line #%lu: %s\n",
+				m_progress + 1, qchars (static_cast<LDGibberish*> (obj)->reason ));
 			
 			logf (LOG_Warning, "- Line was: %s\n", qchars (line));
+*/
 			
-			if (m_warningsPointer)
-				(*m_warningsPointer)++;
+			if( m_warningsPointer )
+				( *m_warningsPointer )++;
 		}
 		
 		m_objs << obj;
 		m_progress++;
-		emit progressUpdate (m_progress);
+		emit progressUpdate( m_progress );
 		
-		if (abortflag) {
+		if( abortflag )
+		{
 			// We were flagged for abortion, so abort.
-			for (LDObject* obj : m_objs)
+			for( LDObject* obj : m_objs )
 				delete obj;
 			
-			m_objs.clear ();
+			m_objs.clear();
 			return;
 		}
 	}
 	
-	emit workDone ();
+	emit workDone();
 	m_done = true;
 }
 
@@ -238,23 +258,22 @@
 	vector<str> lines;
 	vector<LDObject*> objs;
 	
-	if (numWarnings)
+	if( numWarnings )
 		*numWarnings = 0;
 	
 	FileLoader* loader = new FileLoader;
-	loader->setFile (f);
-	loader->setWarningsPointer (numWarnings);
+	loader->setFile( f );
+	loader->setWarningsPointer( numWarnings );
 	
 	// Calculate the amount of lines
 	ulong numLines = 0;
-	for (str line : *f) {
-		(void) line;
+	for( str line : *f )
 		numLines++;
-	}
+	
+	f->rewind();
 	
-	f->rewind ();
-	
-	if (g_loadingMainFile) {
+	if (g_loadingMainFile)
+	{
 		// Show a progress dialog if we're loading the main file here and move
 		// the actual work to a separate thread as this can be a rather intensive
 		// operation and if we don't respond quickly enough, the program can be
@@ -262,32 +281,32 @@
 		
 		// Init the thread and move the loader into it
 		QThread* loaderThread = new QThread;
-		QObject::connect (loaderThread, SIGNAL (started ()), loader, SLOT (work ()));
-		QObject::connect (loaderThread, SIGNAL (finished ()), loader, SLOT (deleteLater ()));
+		QObject::connect( loaderThread, SIGNAL( started() ), loader, SLOT( work() ));
+		QObject::connect( loaderThread, SIGNAL( finished() ), loader, SLOT( deleteLater() ));
 		loader->moveToThread (loaderThread);
 		loaderThread->start ();
 		
-		// Now create a progress dialog for the operation
+		// Now create a progress prompt for the operation
 		OpenProgressDialog* dlg = new OpenProgressDialog (g_win);
-		dlg->setNumLines (numLines);
+		dlg->setNumLines( numLines );
 		
-		// Connect the loader in so we can actually show updates
-		QObject::connect (loader, SIGNAL (progressUpdate (int)), dlg, SLOT (updateProgress (int)));
-		QObject::connect (loader, SIGNAL (workDone ()), dlg, SLOT (accept ()));
+		// Connect the loader in so we can show updates
+		QObject::connect( loader, SIGNAL( progressUpdate( int )), dlg, SLOT( updateProgress( int )));
+		QObject::connect( loader, SIGNAL( workDone() ), dlg, SLOT( accept() ));
 		
-		// Show the dialog. If the user hits cancel, tell the loader to abort.
-		if (!dlg->exec ())
+		// Show the prompt. If the user hits cancel, tell the loader to abort.
+		if( !dlg->exec() )
 			loader->abortflag = true;
 	} else
-		loader->work ();
+		loader->work();
 	
 	// If we wanted the success value, supply that now
-	if (ok)
-		*ok = loader->done ();
+	if( ok )
+		*ok = loader->done();
 	
 	// If the loader was done, return the objects it generated
-	if (loader->done ())
-		objs = loader->objs ();
+	if( loader->done() )
+		objs = loader->objs();
 	
 	return objs;
 }
@@ -295,39 +314,43 @@
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-LDOpenFile* openDATFile (str path, bool search) {
+LDOpenFile* openDATFile( str path, bool search )
+{
 	// Convert the file name to lowercase since some parts contain uppercase
 	// file names. I'll assume here that the library will always use lowercase
 	// file names for the actual parts..
 	File* f;
-	if (search)
+	if( search )
 		f = openLDrawFile (path.toLower (), true);
 	else {
-		f = new File (path, File::Read);
+		f = new File( path, File::Read );
 		
-		if (!*f) {
+		if( !*f )
+		{
 			delete f;
 			f = null;
 		}
 	}
 	
-	if (!f)
+	if( !f )
 		return null;
 	
 	LDOpenFile* oldLoad = g_curfile;
 	LDOpenFile* load = new LDOpenFile;
-	load->setName (path);
+	load->setName( path );
 	
-	if (g_loadingMainFile) {
+	if( g_loadingMainFile )
+	{
 		g_curfile = load;
-		g_win->R ()->setFile (load);
+		g_win->R()->setFile( load );
 	}
 	
 	ulong numWarnings;
 	bool ok;
 	vector<LDObject*> objs = loadFileContents (f, &numWarnings, &ok);
 	
-	if (!ok) {
+	if( !ok )
+	{
 		load = oldLoad;
 		return null;
 	}
@@ -338,8 +361,10 @@
 	delete f;
 	g_loadedFiles << load;
 	
+	/*
 	logf ("File %s parsed successfully (%lu warning%s).\n",
 		qchars (path), numWarnings, plural (numWarnings));
+	*/
 	
 	return load;
 }
@@ -347,34 +372,38 @@
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-bool LDOpenFile::safeToClose () {
-	setlocale (LC_ALL, "C");
+bool LDOpenFile::safeToClose()
+{
+	typedef QMessageBox msgbox;
+	setlocale( LC_ALL, "C" );
 	
 	// 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 %1. Should it be saved?",
-			(name ().length () > 0) ? name () : "<anonymous>"),
-			(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel), QMessageBox::Cancel))
+	if( !implicit() && history().pos() != savePos() )
+	{
+		str message = fmt( "There are unsaved changes to %1. Should it be saved?",
+			(name().length() > 0) ? name() : "<anonymous>");
+		switch( msgbox::question( g_win, "Unsaved Changes", message,
+			( msgbox::Yes | msgbox::No | msgbox::Cancel ), msgbox::Cancel ))
 		{
-		case QMessageBox::Yes:
+		case msgbox::Yes:
 			// If we don't have a file path yet, we have to ask the user for one.
-			if (name ().length () == 0) {
-				str newpath = QFileDialog::getSaveFileName (g_win, "Save As",
-					g_curfile->name (), "LDraw files (*.dat *.ldr)");
+			if( name().length() == 0 )
+			{
+				str newpath = QFileDialog::getSaveFileName( g_win, "Save As",
+					g_curfile->name(), "LDraw files (*.dat *.ldr)" );
 				
-				if (newpath.length () == 0)
+				if( newpath.length() == 0 )
 					return false;
 				
-				setName (newpath);
+				setName( newpath );
 			}
 			
-			if (!save ()) {
-				str errormsg = fmt ("Failed to save %1: %2\nDo you still want to close?",
-					name (), strerror (errno));
+			if( !save () ) {
+				message = 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)
+				if( QMessageBox::critical( g_win, "Save Failure", message,
+					( QMessageBox::Yes | QMessageBox::No ), QMessageBox::No ) == QMessageBox::No)
 				{
 					return false;
 				}
@@ -382,7 +411,7 @@
 			
 			break;
 		
-		case QMessageBox::Cancel:
+		case msgbox::Cancel:
 			return false;
 		
 		default:
@@ -396,180 +425,183 @@
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-void closeAll () {
-	if (!g_loadedFiles.size())
+void closeAll()
+{
+	if( !g_loadedFiles.size() )
 		return;
 	
 	// Remove all loaded files and the objects they contain
-	for (LDOpenFile* file : g_loadedFiles)
+	for( LDOpenFile* file : g_loadedFiles )
 		delete file;
 	
 	// Clear the array
 	g_loadedFiles.clear();
 	g_curfile = null;
 	
-	g_win->R ()->setFile (null);
-	g_win->fullRefresh ();
+	g_win->R()->setFile (null);
+	g_win->fullRefresh();
 }
 
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-void newFile () {
-	// Create a new anonymous file and set it to our current
-	closeAll ();
+void newFile ()
+{
+	closeAll();
 	
+	// Create a new anonymous file and set it to our current
 	LDOpenFile* f = new LDOpenFile;
-	f->setName ("");
-	f->setImplicit (false);
+	f->setName( "" );
+	f->setImplicit( false );
 	g_loadedFiles << f;
 	g_curfile = f;
 	
-	g_BBox.reset ();
-	g_win->R ()->setFile (f);
-	g_win->fullRefresh ();
-	g_win->updateTitle ();
-	f->history ().updateActions ();
+	g_BBox.reset();
+	g_win->R()->setFile( f );
+	g_win->fullRefresh();
+	g_win->updateTitle();
+	f->history().updateActions();
 }
 
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-void addRecentFile (str path) {
-	QStringList rfiles = io_recentfiles.value.split ('@');
-	
-	int idx = 0;
+void addRecentFile( str path )
+{
+	QStringList rfiles = io_recentfiles.value.split( '@' );
 	
-	for (str& it : rfiles) {
-		if (it == path)
-			break;
-		
-		idx++;
-	}
+	int idx = rfiles.indexOf( path );
 	
 	// If this file already is in the list, pop it out.
-	if (idx != rfiles.size ()) {
-		if (rfiles.size () == 1)
-			return; // only recent file - do nothing
+	if( idx != -1 )
+	{
+		if( rfiles.size () == 1 )
+			return; // only recent file - abort and do nothing
 		
 		// Pop it out.
-		rfiles.removeAt (idx);
+		rfiles.removeAt( idx );
 	}
 	
 	// If there's too many recent files, drop one out.
-	while (rfiles.size () > (5 - 1))
-		rfiles.removeAt (0);
+	while( rfiles.size() > ( g_MaxRecentFiles - 1 ))
+		rfiles.removeAt( 0 );
 	
 	// Add the file
-	rfiles.push_back (path);
+	rfiles.push_back( path );
 	
 	// Rebuild the config string
-	io_recentfiles = rfiles.join ("@");
+	io_recentfiles = rfiles.join( "@" );
 	
-	config::save ();
-	g_win->updateRecentFilesMenu ();
+	config::save();
+	g_win->updateRecentFilesMenu();
 }
 
 // =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// -----------------------------------------------------------------------------
+// Open an LDraw file and set it as the main model
 // =============================================================================
-void openMainFile (str path) {
+void openMainFile( str path )
+{
 	g_loadingMainFile = true;
-	closeAll ();
+	closeAll();
 	
-	LDOpenFile* file = openDATFile (path, false);
+	LDOpenFile* file = openDATFile( path, false );
 	
-	if (!file) {
+	if (!file)
+	{
 		// Loading failed, thus drop down to a new file since we
 		// closed everything prior.
 		newFile ();
 		
 		// Tell the user loading failed.
-		setlocale (LC_ALL, "C");
-		critical (fmt ("Failed to open %1: %2", path, strerror (errno)));
+		setlocale( LC_ALL, "C" );
+		critical( fmt( "Failed to open %1: %2", path, strerror( errno )));
 		
 		g_loadingMainFile = false;
 		return;
 	}
 	
-	file->setImplicit (false);
+	file->setImplicit( false );
 	g_curfile = file;
 	
 	// Recalculate the bounding box
 	g_BBox.calculate();
 	
 	// Rebuild the object tree view now.
-	g_win->fullRefresh ();
-	g_win->updateTitle ();
-	g_win->R ()->setFile (file);
-	g_win->R ()->resetAngles ();
+	g_win->fullRefresh();
+	g_win->updateTitle();
+	g_win->R()->setFile( file );
+	g_win->R()->resetAngles();
 	
 	// Add it to the recent files list.
-	addRecentFile (path);
+	addRecentFile( path );
 	g_loadingMainFile = false;
 }
 
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-bool LDOpenFile::save (str savepath) {
-	if (!savepath.length ())
-		savepath = name ();
+bool LDOpenFile::save( str savepath )
+{
+	if( !savepath.length() )
+		savepath = name();
 	
-	File f (savepath, File::Write);
+	File f( savepath, File::Write );
 	
-	if (!f)
+	if( !f )
 		return false;
 	
 	// If the second object in the list holds the file name, update that now.
 	// Only do this if the file is explicitly open. If it's saved into a directory
 	// called "s" or "48", prepend that into the name.
 	LDComment* fpathComment = null;
-	if (!implicit () && objs ().size () >= 2 && object (1)->getType () == LDObject::Comment) {
-		fpathComment = static_cast<LDComment*> (object (1));
+	LDObject* first = object( 1 );
+	if( !implicit() && first != null && first->getType() == LDObject::Comment )
+	{
+		fpathComment = static_cast<LDComment*>( first );
 		
-		if (fpathComment->text.left (6) == "Name: ") {
-			str newfname;
-			str dir = basename (dirname (savepath));
+		if( fpathComment->text.left( 6 ) == "Name: " ) {
+			str newname;
+			str dir = basename( dirname( savepath ));
 			
-			if (dir == "s" || dir == "48")
-				newfname = dir + "\\";
+			if( dir == "s" || dir == "48" )
+				newname = dir + "\\";
 			
-			newfname += basename (savepath);
-			fpathComment->text = fmt ("Name: %1", newfname);
-			g_win->buildObjList ();
+			newname += basename( savepath );
+			fpathComment->text = fmt( "Name: %1", newname );
+			g_win->buildObjList();
 		}
 	}
 	
-	// Write all entries now
-	for (LDObject* obj : objs ()) {
-		// LDraw requires files to have DOS line endings
-		f.write (obj->raw () + "\r\n");
-	}
+	// File is open, now save the model to it. Note that LDraw requires files to
+	// have DOS line endings, so we terminate the lines with \r\n.
+	for( LDObject* obj : objs() )
+		f.write( obj->raw () + "\r\n" );
 	
-	f.close ();
+	// File is saved, now clean up.
+	f.close();
 	
 	// We have successfully saved, update the save position now.
-	setSavePos (history ().pos ());
-	setName (savepath);
+	setSavePos( history().pos() );
+	setName( savepath );
 	
-	g_win->updateTitle ();
+	g_win->updateTitle();
 	return true;
 }
 
-#define CHECK_TOKEN_COUNT(N) \
-	if (tokens.size() != N) \
-		return new LDGibberish (line, "Bad amount of tokens");
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+#define CHECK_TOKEN_COUNT( N ) \
+	if( tokens.size() != N ) \
+		return new LDGibberish( line, "Bad amount of tokens" );
 
-#define CHECK_TOKEN_NUMBERS(MIN,MAX) \
+#define CHECK_TOKEN_NUMBERS( MIN, MAX ) \
 	for (ushort i = MIN; i <= MAX; ++i) \
 		if (!isNumber (tokens[i])) \
 			return new LDGibberish (line, fmt ("Token #%1 was `%2`, expected a number", \
 				(i + 1), tokens[i]));
 
-// =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-// =============================================================================
 static vertex parseVertex( QStringList& s, const ushort n )
 {
 	vertex v;
@@ -580,31 +612,35 @@
 }
 
 // =============================================================================
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// -----------------------------------------------------------------------------
+// This is the LDraw code parser function. It takes in a string containing LDraw
+// code and returns the object parsed from it. parseLine never returns null,
+// the object will be LDGibberish if it could not be parsed properly.
 // =============================================================================
-LDObject* parseLine (str line) {
-	QStringList tokens = line.split (" ", str::SkipEmptyParts);
+LDObject* parseLine( str line )
+{
+	QStringList tokens = line.split( " ", str::SkipEmptyParts );
 	
-	if (!tokens.size ()) {
+	if( tokens.size() <= 0 )
+	{
 		// Line was empty, or only consisted of whitespace
 		return new LDEmpty;
 	}
 	
-	if (tokens[0].length () != 1)
+	if( tokens[0].length() != 1 )
 		return new LDGibberish (line, "Illogical line code");
 	
-	const qchar c = tokens[0][0];
-	switch (c.toAscii () - '0') {
+	int num = tokens[0][0].toAscii() - '0';
+	switch( num )
+	{
 	case 0:
 		{
 			// Comment
-			str comm;
-			for (int i = 1; i < tokens.size(); ++i) {
-				comm += tokens[i];
-				
-				if (i != tokens.size() - 1)
-					comm += ' ';
-			}
+			str comm = line.mid( line.indexOf( "0" ) + 1 );
+			
+			// Remove any leading whitespace
+			while( comm[0] == ' ' )
+				comm.remove( 0, 1 );
 			
 			// Handle BFC statements
 			if (tokens.size() > 2 && tokens[1] == "BFC") {
@@ -619,10 +655,11 @@
 					return new LDBFC (LDBFC::InvertNext);
 			}
 			
-			if (tokens.size() > 2 && tokens[1] == "!LDFORGE") {
-				// Handle LDForge-specific types, they're embedded into comments
-				
-				if (tokens[2] == "VERTEX") {
+			if( tokens.size() > 2 && tokens[1] == "!LDFORGE" )
+			{
+				// Handle LDForge-specific types, they're embedded into comments too
+				if( tokens[2] == "VERTEX" )
+				{
 					// Vertex (0 !LDFORGE VERTEX)
 					CHECK_TOKEN_COUNT (7)
 					CHECK_TOKEN_NUMBERS (3, 6)
@@ -636,40 +673,45 @@
 					return obj;
 				}
 				
-				if (tokens[2] == "RADIAL") {
+				if( tokens[2] == "RADIAL" )
+				{
 					CHECK_TOKEN_COUNT (20)
 					CHECK_TOKEN_NUMBERS (4, 19)
 					
-					LDRadial::Type eType = LDRadial::NumTypes;
+					LDRadial::Type radtype = LDRadial::NumTypes;
 					
-					for (int i = 0; i < LDRadial::NumTypes; ++i) {
-						if (str (LDRadial::radialTypeName ((LDRadial::Type) i)).toUpper ().remove (' ') == tokens[3]) {
-							eType = (LDRadial::Type) i;
+					for( int i = 0; i < LDRadial::NumTypes; ++i )
+					{
+						str radname = LDRadial::radialTypeName( (LDRadial::Type) i );
+						if( radname.toUpper().remove( ' ' ) == tokens[3] )
+						{
+							radtype = (LDRadial::Type) i;
 							break;
 						}
 					}
 					
-					if (eType == LDRadial::NumTypes)
+					if ( radtype == LDRadial::NumTypes)
 						return new LDGibberish (line, fmt ("Unknown radial type %1", tokens[3]));
 					
 					LDRadial* obj = new LDRadial;
 					
-					obj->setType (eType);
-					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
+					obj->setType( radtype );
+					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] = tokens[i + 11].toDouble (); // 11 - 19
+					for( short i = 0; i < 9; ++i )
+						transform[i] = tokens[i + 11].toDouble(); // 11 - 19
 					
-					obj->setTransform (transform);
+					obj->setTransform( transform );
 					return obj;
 				}
 			}
 			
+			// Just a regular comment:
 			LDComment* obj = new LDComment;
 			obj->text = comm;
 			return obj;
@@ -678,106 +720,93 @@
 	case 1:
 		{
 			// Subfile
-			CHECK_TOKEN_COUNT (15)
-			CHECK_TOKEN_NUMBERS (1, 13)
+			CHECK_TOKEN_COUNT( 15 )
+			CHECK_TOKEN_NUMBERS( 1, 13 )
 			
 			// Try open the file. Disable g_loadingMainFile temporarily since we're
 			// not loading the main file now, but the subfile in question.
 			bool tmp = g_loadingMainFile;
 			g_loadingMainFile = false;
-			LDOpenFile* load = getFile (tokens[14]);
+			LDOpenFile* load = getFile( tokens[14] );
 			g_loadingMainFile = tmp;
 			
 			// If we cannot open the file, mark it an error
-			if (!load)
-				return new LDGibberish (line, "Could not open referred file");
+			if( !load )
+				return new LDGibberish( line, "Could not open referred file" );
 			
 			LDSubfile* obj = new LDSubfile;
-			obj->setColor (tokens[1].toLong ());
-			obj->setPosition (parseVertex (tokens, 2)); // 2 - 4
+			obj->setColor( tokens[1].toLong() );
+			obj->setPosition( parseVertex(tokens, 2 )); // 2 - 4
 			
 			matrix transform;
-			for (short i = 0; i < 9; ++i)
-				transform[i] = tokens[i + 5].toDouble (); // 5 - 13
+			for( short i = 0; i < 9; ++i )
+				transform[i] = tokens[i + 5].toDouble(); // 5 - 13
 			
-			obj->setTransform (transform);
-			obj->setFileInfo (load);
+			obj->setTransform( transform );
+			obj->setFileInfo( load );
 			return obj;
 		}
 	
 	case 2:
 		{
-			CHECK_TOKEN_COUNT (8)
-			CHECK_TOKEN_NUMBERS (1, 7)
+			CHECK_TOKEN_COUNT( 8 )
+			CHECK_TOKEN_NUMBERS( 1, 7 )
 			
 			// Line
 			LDLine* obj = new LDLine;
-			obj->setColor (tokens[1].toLong ());
-			for (short i = 0; i < 2; ++i)
-				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 7
+			obj->setColor( tokens[1].toLong() );
+			for( short i = 0; i < 2; ++i )
+				obj->setVertex( i, parseVertex( tokens, 2 + ( i * 3 ))); // 2 - 7
 			return obj;
 		}
 	
 	case 3:
 		{
-			CHECK_TOKEN_COUNT (11)
-			CHECK_TOKEN_NUMBERS (1, 10)
+			CHECK_TOKEN_COUNT( 11 )
+			CHECK_TOKEN_NUMBERS( 1, 10 )
 			
 			// Triangle
 			LDTriangle* obj = new LDTriangle;
-			obj->setColor (tokens[1].toLong ());
+			obj->setColor( tokens[1].toLong() );
 			
-			for (short i = 0; i < 3; ++i)
-				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 10
+			for( short i = 0; i < 3; ++i )
+				obj->setVertex( i, parseVertex( tokens, 2 + ( i * 3 ))); // 2 - 10
 			
 			return obj;
 		}
 	
 	case 4:
-		{
-			CHECK_TOKEN_COUNT (14)
-			CHECK_TOKEN_NUMBERS (1, 13)
-			
-			// Quadrilateral
-			LDQuad* obj = new LDQuad;
-			obj->setColor (tokens[1].toLong ());
-			
-			for (short i = 0; i < 4; ++i)
-				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13
-			
-			return obj;
-		}
-	
 	case 5:
 		{
-			CHECK_TOKEN_COUNT (14)
-			CHECK_TOKEN_NUMBERS (1, 13)
+			CHECK_TOKEN_COUNT( 14 )
+			CHECK_TOKEN_NUMBERS( 1, 13 )
 			
-			// Conditional line
-			LDCondLine* obj = new LDCondLine;
-			obj->setColor (tokens[1].toLong ());
+			// Quadrilateral / Conditional line
+			LDObject* obj = ( num == 4 ) ? ( (LDObject*) new LDQuad ) : ( (LDObject*) new LDCondLine );
+			obj->setColor( tokens[1].toLong() );
 			
-			for (short i = 0; i < 4; ++i)
-				obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13
+			for( short i = 0; i < 4; ++i )
+				obj->setVertex( i, parseVertex( tokens, 2 + ( i * 3 ))); // 2 - 13
 			
 			return obj;
 		}
 	
 	default: // Strange line we couldn't parse
-		return new LDGibberish (line, "Unknown line code number");
+		return new LDGibberish( line, "Unknown line code number" );
 	}
 }
 
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-LDOpenFile* getFile (str fname) {
+LDOpenFile* getFile( str fname )
+{
 	// Try find the file in the list of loaded files
-	LDOpenFile* load = findLoadedFile (fname);
+	LDOpenFile* load = findLoadedFile( fname );
 	
 	// If it's not loaded, try open it
-	if (!load)
-		load = openDATFile (fname, true);
+	if( !load )
+		load = openDATFile( fname, true );
 	
 	return load;
 }
@@ -786,80 +815,81 @@
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 void reloadAllSubfiles () {
-	if (!g_curfile)
+	if( !g_curfile )
 		return;
 	
-	g_loadedFiles.clear ();
+	g_loadedFiles.clear();
 	g_loadedFiles << g_curfile;
 	
 	// Go through all objects in the current file and reload the subfiles
-	for (LDObject* obj : g_curfile->objs ()) {
-		if (obj->getType() == LDObject::Subfile) {
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDOpenFile* fileInfo = getFile (ref->fileInfo ()->name ());
+	for( LDObject* obj : g_curfile->objs() )
+	{
+		if( obj->getType() == LDObject::Subfile )
+		{
+			LDSubfile* ref = static_cast<LDSubfile*>( obj );
+			LDOpenFile* fileInfo = getFile( ref->fileInfo()->name() );
 			
 			if (fileInfo)
-				ref->setFileInfo (fileInfo);
-			else {
-				// Couldn't load the file, mark it an error
-				ref->replace (new LDGibberish (ref->raw (), "Could not open referred file"));
-			}
+				ref->setFileInfo( fileInfo );
+			else
+				ref->replace( new LDGibberish( ref->raw(), "Could not open referred file" ));
 		}
 		
 		// Reparse gibberish files. It could be that they are invalid because
 		// of loading errors. Circumstances may be different now.
-		if (obj->getType () == LDObject::Gibberish)
-			obj->replace (parseLine (static_cast<LDGibberish*> (obj)->contents));
+		if( obj->getType() == LDObject::Gibberish )
+			obj->replace( parseLine( static_cast<LDGibberish*>( obj )->contents ));
 	}
 	
 	// Close all files left unused
-	LDOpenFile::closeUnused ();
+	LDOpenFile::closeUnused();
 }
 
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-ulong LDOpenFile::addObject (LDObject* obj) {
-	PROP_NAME (history).add (new AddHistory (PROP_NAME (objs).size (), obj));
-	PROP_NAME (objs) << obj;
+ulong LDOpenFile::addObject (LDObject* obj)
+{
+	PROP_NAME( history ).add( new AddHistory( PROP_NAME( objs ).size(), obj ));
+	PROP_NAME( objs ) << obj;
 	
-	if (obj->getType () == LDObject::Vertex)
-		PROP_NAME (vertices) << obj;
+	if( obj->getType() == LDObject::Vertex )
+		PROP_NAME( vertices ) << obj;
 	
-	if (this == g_curfile)
-		g_BBox.calcObject (obj);
+	if( this == g_curfile )
+		g_BBox.calcObject( obj );
 	
-	return numObjs () - 1;
+	return numObjs() - 1;
 }
 
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
 void LDOpenFile::insertObj (const ulong pos, LDObject* obj) {
-	m_history.add (new AddHistory (pos, obj));
-	m_objs.insert (pos, obj);
+	m_history.add( new AddHistory( pos, obj ));
+	m_objs.insert( pos, obj );
 	
-	if (this == g_curfile)
-		g_BBox.calcObject (obj);
+	if( this == g_curfile )
+		g_BBox.calcObject( obj );
 }
 
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
-void LDOpenFile::forgetObject (LDObject* obj) {
-	ulong idx = obj->getIndex (this);
-	m_history.add (new DelHistory (idx, obj));
-	m_objs.erase (idx);
+void LDOpenFile::forgetObject( LDObject* obj ) {
+	ulong idx = obj->getIndex( this );
+	m_history.add( new DelHistory( idx, obj ));
+	m_objs.erase( idx );
 	
 	// Update the bounding box
-	if (this == g_curfile)
+	if( this == g_curfile )
 		g_BBox.calculate ();
 }
 
 // =============================================================================
 bool safeToCloseAll () {
-	for (LDOpenFile* f : g_loadedFiles)
-		if (!f->safeToClose ())
+	for( LDOpenFile* f : g_loadedFiles )
+		if( !f->safeToClose() )
 			return false;
 	
 	return true;
@@ -867,11 +897,12 @@
 
 // =============================================================================
 void LDOpenFile::setObject (ulong idx, LDObject* obj) {
-	assert (idx < numObjs ());
+	assert( idx < numObjs() );
 	
-	str oldcode = m_objs[idx]->raw ();
-	str newcode = obj->raw ();
-	m_history << new EditHistory (idx, oldcode, newcode);
+	// Mark this change to history
+	str oldcode = object( idx )->raw ();
+	str newcode = obj->raw();
+	m_history << new EditHistory( idx, oldcode, newcode );
 	
 	m_objs[idx] = obj;
 }
@@ -879,13 +910,14 @@
 static vector<LDOpenFile*> getFilesUsed (LDOpenFile* node) {
 	vector<LDOpenFile*> filesUsed;
 	
-	for (LDObject* obj : *node) {
-		if (obj->getType () != LDObject::Subfile)
+	for (LDObject* obj : *node)
+	{
+		if( obj->getType() != LDObject::Subfile )
 			continue;
 		
-		LDSubfile* ref = static_cast<LDSubfile*> (obj);
-		filesUsed << ref->fileInfo ();
-		filesUsed << getFilesUsed (ref->fileInfo ());
+		LDSubfile* ref = static_cast<LDSubfile*>( obj );
+		filesUsed << ref->fileInfo();
+		filesUsed << getFilesUsed( ref->fileInfo() );
 	}
 	
 	return filesUsed;
@@ -893,32 +925,44 @@
 
 // =============================================================================
 // Find out which files are unused and close them.
-void LDOpenFile::closeUnused () {
+void LDOpenFile::closeUnused ()
+{
 	vector<LDOpenFile*> filesUsed = getFilesUsed (g_curfile);
 	
-	// Also, anything that's explicitly opened must not be closed
-	for (LDOpenFile* file : g_loadedFiles)
-		if (file->implicit () == false)
+	// Anything that's explicitly opened must not be closed
+	for( LDOpenFile* file : g_loadedFiles )
+		if( !file->implicit() )
 			filesUsed << file;
 	
 	// Remove duplicated entries
-	filesUsed.makeUnique ();
+	filesUsed.makeUnique();
 	
 	// Close all open files that aren't in filesUsed
-	for (LDOpenFile* file : g_loadedFiles) {
+	for( LDOpenFile* file : g_loadedFiles )
+	{
 		bool isused = false;
 		
-		for (LDOpenFile* usedFile : filesUsed) {
-			if (file == usedFile) {
+		for( LDOpenFile* usedFile : filesUsed )
+		{
+			if( file == usedFile )
+			{
 				isused = true;
 				break;
 			}
 		}
 		
-		if (!isused)
+		if( !isused )
 			delete file;
 	}
 	
-	g_loadedFiles.clear ();
+	g_loadedFiles.clear();
 	g_loadedFiles << filesUsed;
+}
+
+LDObject* LDOpenFile::object( ulong pos ) const
+{
+	if( m_objs.size() <= pos )
+		return null;
+	
+	return m_objs[pos];
 }
\ No newline at end of file

mercurial