- rewrote codegen in python and merged updaterevision into it

Tue, 03 Mar 2015 21:54:57 +0200

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Tue, 03 Mar 2015 21:54:57 +0200
changeset 933
f4c80d92e71e
parent 932
738673e8a6b4
child 934
be8128aff739

- rewrote codegen in python and merged updaterevision into it
- renamed editmodes/*.cc to .cpp, forgot about those

CMakeLists.txt file | annotate | diff | comparison | revisions
codegen.py file | annotate | diff | comparison | revisions
codegen/CMakeLists.txt file | annotate | diff | comparison | revisions
codegen/codegen.cpp file | annotate | diff | comparison | revisions
src/configuration.cpp file | annotate | diff | comparison | revisions
src/editmodes/abstractEditMode.cc file | annotate | diff | comparison | revisions
src/editmodes/abstractEditMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/circleMode.cc file | annotate | diff | comparison | revisions
src/editmodes/circleMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/drawMode.cc file | annotate | diff | comparison | revisions
src/editmodes/drawMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/magicWandMode.cc file | annotate | diff | comparison | revisions
src/editmodes/magicWandMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/rectangleMode.cc file | annotate | diff | comparison | revisions
src/editmodes/rectangleMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/selectMode.cc file | annotate | diff | comparison | revisions
src/editmodes/selectMode.cpp file | annotate | diff | comparison | revisions
updaterevision.py file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Tue Mar 03 17:42:21 2015 +0200
+++ b/CMakeLists.txt	Tue Mar 03 21:54:57 2015 +0200
@@ -6,8 +6,8 @@
 ######################################################################
 
 project (ldforge)
-add_subdirectory (codegen)
 cmake_minimum_required (VERSION 2.6)
+cmake_policy (SET CMP0020 OLD)
 
 option (TRANSPARENT_DIRECT_COLORS "Enables non-standard transparent direct colors" OFF)
 option (USE_QT5 "Use Qt5 instead of Qt4" OFF)
@@ -23,8 +23,6 @@
 endif()
 
 find_package (OpenGL REQUIRED)
-
-get_target_property (CODEGEN_EXE codegen LOCATION)
 include_directories (${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
 
 set (LDFORGE_SOURCES
@@ -127,11 +125,6 @@
 	src/ytruder.ui
 )
 
-add_custom_target (codegeneration ALL
-	COMMAND ${CODEGEN_EXE} ${LDFORGE_SOURCES} ${CMAKE_BINARY_DIR}/configuration.inc
-	WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
-	DEPENDS codegen)
-
 set (LDFORGE_RESOURCES ldforge.qrc)
 # set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lGLU")
 
@@ -187,11 +180,23 @@
 	)
 endif()
 
-add_dependencies (${PROJECT_NAME} revision_check codegeneration)
 install (TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
 
+#
+# Code generators
+#
+
 add_custom_target (make_hginfo_h
 	COMMAND python
-		"${CMAKE_SOURCE_DIR}/updaterevision.py"
-		"${CMAKE_CURRENT_BINARY_DIR}/hginfo.h")
-add_dependencies (${PROJECT_NAME} make_hginfo_h)
+		"${CMAKE_SOURCE_DIR}/codegen.py" "hginfo"
+		"${CMAKE_CURRENT_BINARY_DIR}/hginfo.h"
+	WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+
+add_custom_target (make_config_aux
+	COMMAND python
+		"${CMAKE_SOURCE_DIR}/codegen.py" "configaux"
+		"${CMAKE_CURRENT_BINARY_DIR}/config.aux"
+		${LDFORGE_SOURCES}
+	WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+
+add_dependencies (${PROJECT_NAME} make_hginfo_h make_config_aux)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codegen.py	Tue Mar 03 21:54:57 2015 +0200
@@ -0,0 +1,154 @@
+#
+#	Copyright 2015 Teemu Piippo
+#	All rights reserved.
+#
+#	Redistribution and use in source and binary forms, with or without
+#	modification, are permitted provided that the following conditions
+#	are met:
+#
+#	1. Redistributions of source code must retain the above copyright
+#	   notice, this list of conditions and the following disclaimer.
+#	2. Redistributions in binary form must reproduce the above copyright
+#	   notice, this list of conditions and the following disclaimer in the
+#	   documentation and/or other materials provided with the distribution.
+#	3. Neither the name of the copyright holder nor the names of its
+#	   contributors may be used to endorse or promote products derived from
+#	   this software without specific prior written permission.
+#
+#	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+#	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+#	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+#	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
+#	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+#	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+#	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+#	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+#	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+#	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+#	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+import md5
+import re
+import sys
+import os
+
+class OutputFile:
+	def __init__(self, filename):
+		self.filename = filename
+		try:
+			with open (self.filename, "r") as fp:
+				self.oldsum = fp.readline().replace ('\n', '').replace ('// ', '')
+		except IOError:
+			self.oldsum = ''
+
+		self.body = ''
+
+	def write(self, a):
+		self.body += a
+
+	def save(self):
+		checksum = md5.new (self.body).hexdigest()
+
+		if checksum == self.oldsum:
+			print '%s is up to date' % self.filename
+			return False
+
+		with open (self.filename, "w") as fp:
+			fp.write ('// %s\n' % checksum)
+			fp.write (self.body)
+			return True
+
+def prog_configaux():
+	class CfgEntry:
+		def __init__(self, type, name, defvalue):
+			self.type = type
+			self.name = name
+			self.defvalue = defvalue
+
+	if len (sys.argv) < 3:
+		print 'Usage: %s <output> <input1> [input2] [input3] ... [input-n]' % sys.argv[0]
+		quit(1)
+
+	f = OutputFile (sys.argv[1])
+	f.write ('#pragma once\n')
+
+	entries = []
+
+	for inputname in sys.argv[2:]:
+		fp = open (inputname, 'r')
+
+		for line in fp.readlines():
+			match = re.match (r'CFGENTRY\s*\(([^,]+),\s*([^,]+),\s*([^)]+)\)', line)
+			if match:
+				entries.append (CfgEntry (match.group (1), match.group (2), match.group (3)))
+
+	for e in entries:
+		f.write ('EXTERN_CFGENTRY (%s, %s)\n' % (e.type, e.name))
+
+	f.write ('\n')
+	f.write ('static void InitConfigurationEntry (class AbstractConfigEntry* entry);\n')
+	f.write ('static void SetupConfigurationLists()\n')
+	f.write ('{\n')
+
+	for e in entries:
+		f.write ('\tInitConfigurationEntry (new %sConfigEntry (&cfg::%s, \"%s\", %s));\n'
+			% (e.type, e.name, e.name, e.defvalue))
+
+	f.write ('}\n')
+
+	if f.save():
+		print 'Wrote configuration aux code to %s' % f.filename
+
+def prog_hginfo():
+	import subprocess
+	from datetime import datetime
+
+	if len (sys.argv) != 2:
+		print 'usage: %s <output>' % sys.argv[0]
+		quit (1)
+
+	f = OutputFile (sys.argv[1])
+	data = subprocess.check_output (['hg', 'log', '-r.', '--template',
+		'{node|short} {branch} {date|hgdate}']).replace ('\n', '').split (' ')
+
+	rev = data[0]
+	branch = data[1]
+	timestamp = int (data[2])
+	date = datetime.utcfromtimestamp (timestamp)
+	datestring = date.strftime ('%y%m%d-%H%M') if date.year >= 2000 else '000000-0000'
+
+	if len(rev) > 7:
+		rev = rev[0:7]
+
+	if subprocess.check_output (['hg', 'id', '-n']).replace ('\n', '')[-1] == '+':
+		rev += '+'
+
+	f.write ('#define HG_NODE "%s"\n' % rev)
+	f.write ('#define HG_BRANCH "%s"\n' % branch)
+	f.write ('#define HG_DATE_VERSION "%s"\n' % datestring)
+	f.write ('#define HG_DATE_STRING "%s"\n' % date.strftime ('%d %b %Y'))
+	f.write ('#define HG_DATE_TIME %d\n' % int (timestamp))
+	if f.save():
+		print '%s updated to %s' % (f.filename, rev)
+
+def main():
+	if len(sys.argv) < 2:
+		print 'You must give a program name'
+		quit(1)
+
+	progname = sys.argv[1]
+	sys.argv[0] = '%s %s' % (sys.argv[0], sys.argv[1])
+	sys.argv.pop (1)
+
+	impl = globals().copy()
+	impl.update (locals())
+	method = impl.get ('prog_' + progname)
+
+	if method:
+		method()
+	else:
+		print 'no such program %s' % progname
+
+if __name__ == '__main__':
+	main()
\ No newline at end of file
--- a/codegen/CMakeLists.txt	Tue Mar 03 17:42:21 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-cmake_minimum_required (VERSION 2.4)
-add_executable (codegen codegen.cpp)
-
-# 
-# LDForge uses alternative operators. GCC and Clang use these by default but MSVC does not.
-# So we'll have to tell MSVC to use these alternative operators
-# 
-if (MSVC)
-	set_target_properties (codegen PROPERTIES COMPILE_FLAGS "/Za")
-endif()
--- a/codegen/codegen.cpp	Tue Mar 03 17:42:21 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <sstream>
-#include <fstream>
-#include <string>
-#include <cstring>
-#include <iostream>
-#include <vector>
-#include <algorithm>
-
-using std::string;
-using std::vector;
-using std::ifstream;
-using std::size_t;
-using std::strncmp;
-using std::getline;
-using std::cout;
-using std::endl;
-
-struct EntryType
-{
-	string name;
-	string type;
-	string defvalue;
-
-	inline bool operator< (EntryType const& other) const
-	{
-		return name < other.name;
-	}
-
-	inline bool operator== (EntryType const& other) const
-	{
-		return name == other.name and type == other.type;
-	}
-
-	inline bool operator!= (EntryType const& other) const
-	{
-		return not operator== (other);
-	}
-};
-
-char AdvancePointer (char const*& ptr)
-{
-	char a = *ptr++;
-
-	if (*ptr == '\0')
-		throw false;
-
-	return a;
-}
-
-void ReadConfigEntries (string const& filename, vector<EntryType>& entries, const char* macroname)
-{
-	ifstream is (filename.c_str());
-	string line;
-	size_t const macrolen = strlen (macroname);
-
-	while (getline (is, line))
-	{
-		try
-		{
-			if (strncmp (line.c_str(), macroname, macrolen) != 0)
-				continue;
-
-			char const* ptr = &line[macrolen];
-			EntryType entry;
-
-			// Skip to paren
-			while (*ptr != '(')
-				AdvancePointer (ptr);
-
-			// Skip whitespace
-			while (isspace (*ptr))
-				AdvancePointer (ptr);
-
-			// Skip paren
-			AdvancePointer (ptr);
-
-			// Read type
-			while (*ptr != ',')
-				entry.type += AdvancePointer (ptr);
-
-			// Skip comma and whitespace
-			for (AdvancePointer (ptr); isspace (*ptr); AdvancePointer (ptr))
-				;
-
-			// Read name
-			while (*ptr != ',')
-				entry.name += AdvancePointer (ptr);
-
-			// Skip comma and whitespace
-			for (AdvancePointer (ptr); isspace (*ptr); AdvancePointer (ptr))
-				;
-
-			// Read default
-			while (*ptr != ')')
-				entry.defvalue += AdvancePointer (ptr);
-
-			entries.push_back (entry);
-		}
-		catch (bool) {}
-	}
-}
-
-bool CheckEquality (vector<EntryType> a, vector<EntryType> b)
-{
-	if (a.size() != b.size())
-		return false;
-
-	std::sort (a.begin(), a.end());
-	std::sort (b.begin(), b.end());
-
-	for (size_t i = 0; i < a.size(); ++i)
-	{
-		if (a[i] != b[i])
-			return false;
-	}
-
-	return true;
-}
-
-int main (int argc, char* argv[])
-{
-	vector<EntryType> entries;
-	vector<EntryType> oldentries;
-	ReadConfigEntries (argv[argc - 1], oldentries, "CODEGEN_CACHE");
-
-	for (int arg = 1; arg < argc - 1; ++arg)
-		ReadConfigEntries (argv[arg], entries, "CFGENTRY");
-
-	if (CheckEquality (entries, oldentries))
-	{
-		cout << "Configuration options unchanged" << endl;
-		return 0;
-	}
-
-	std::ofstream os (argv[argc - 1]);
-	os << "#pragma once" << endl;
-	os << "#define CODEGEN_CACHE(A,B,C)" << endl;
-
-	for (vector<EntryType>::const_iterator it = entries.begin(); it != entries.end(); ++it)
-	{
-		os << "CODEGEN_CACHE (" << it->type << ", " << it->name << ", " <<
-			  it->defvalue << ")" << endl;
-	}
-
-	os << endl;
-	for (vector<EntryType>::const_iterator it = entries.begin(); it != entries.end(); ++it)
-		os << "EXTERN_CFGENTRY (" << it->type << ", " << it->name << ")" << endl;
-
-	os << endl;
-	os << "static void InitConfigurationEntry (AbstractConfigEntry* entry);" << endl;
-	os << "static void SetupConfigurationLists()" << endl;
-	os << "{" << endl;
-
-	for (vector<EntryType>::const_iterator it = entries.begin(); it != entries.end(); ++it)
-	{
-		os << "\tInitConfigurationEntry (new " << it->type << "ConfigEntry (&cfg::" <<
-			  it->name << ", \"" << it->name << "\", " << it->defvalue << "));" << endl;
-	}
-
-	os << "}" << endl;
-	cout << "Wrote configuration options list to " << argv[argc - 1] << "." << endl;
-	return 0;
-}
--- a/src/configuration.cpp	Tue Mar 03 17:42:21 2015 +0200
+++ b/src/configuration.cpp	Tue Mar 03 21:54:57 2015 +0200
@@ -32,7 +32,7 @@
 #include "mainWindow.h"
 #include "ldDocument.h"
 #include "glRenderer.h"
-#include "configuration.inc"
+#include "config.aux"
 
 #ifdef _WIN32
 # define EXTENSION ".ini"
--- a/src/editmodes/abstractEditMode.cc	Tue Mar 03 17:42:21 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,263 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QMouseEvent>
-#include <stdexcept>
-#include "abstractEditMode.h"
-#include "selectMode.h"
-#include "drawMode.h"
-#include "rectangleMode.h"
-#include "circleMode.h"
-#include "magicWandMode.h"
-#include "linePathMode.h"
-#include "../mainWindow.h"
-#include "../glRenderer.h"
-
-CFGENTRY (Bool, DrawLineLengths, true)
-CFGENTRY (Bool, DrawAngles, false)
-
-AbstractEditMode::AbstractEditMode (GLRenderer* renderer) :
-	m_renderer (renderer) {}
-
-AbstractEditMode::~AbstractEditMode() {}
-
-AbstractEditMode* AbstractEditMode::createByType (GLRenderer* renderer, EditModeType type)
-{
-	switch (type)
-	{
-		case EditModeType::Select: return new SelectMode (renderer);
-		case EditModeType::Draw: return new DrawMode (renderer);
-		case EditModeType::Rectangle: return new RectangleMode (renderer);
-		case EditModeType::Circle: return new CircleMode (renderer);
-		case EditModeType::MagicWand: return new MagicWandMode (renderer);
-		case EditModeType::LinePath: return new LinePathMode (renderer);
-	}
-
-	throw std::logic_error ("bad type given to AbstractEditMode::createByType");
-}
-
-GLRenderer* AbstractEditMode::renderer() const
-{
-	return m_renderer;
-}
-
-AbstractDrawMode::AbstractDrawMode (GLRenderer* renderer) :
-	AbstractEditMode (renderer),
-	m_polybrush (QBrush (QColor (64, 192, 0, 128)))
-{
-	// Disable the context menu - we need the right mouse button
-	// for removing vertices.
-	renderer->setContextMenuPolicy (Qt::NoContextMenu);
-
-	// Use the crosshair cursor when drawing.
-	renderer->setCursor (Qt::CrossCursor);
-
-	// Clear the selection when beginning to draw.
-	CurrentDocument()->clearSelection();
-
-	g_win->updateSelection();
-	m_drawedVerts.clear();
-}
-
-AbstractSelectMode::AbstractSelectMode (GLRenderer* renderer) :
-	AbstractEditMode (renderer)
-{
-	renderer->unsetCursor();
-	renderer->setContextMenuPolicy (Qt::DefaultContextMenu);
-}
-
-// =============================================================================
-//
-void AbstractDrawMode::addDrawnVertex (Vertex const& pos)
-{
-	if (preAddVertex (pos))
-		return;
-
-	m_drawedVerts << pos;
-}
-
-bool AbstractDrawMode::mouseReleased (MouseEventData const& data)
-{
-	if (Super::mouseReleased (data))
-		return true;
-
-	if ((data.releasedButtons & Qt::MidButton) and (m_drawedVerts.size() < 4) and (not data.mouseMoved))
-	{
-		// Find the closest vertex to our cursor
-		double			minimumDistance = 1024.0;
-		const Vertex*	closest = null;
-		Vertex			cursorPosition = renderer()->coordconv2_3 (data.ev->pos(), false);
-		QPoint			cursorPosition2D (data.ev->pos());
-		const Axis		relZ = renderer()->getRelativeZ();
-		QVector<Vertex>	vertices = renderer()->document()->inlineVertices();
-
-		// Sort the vertices in order of distance to camera
-		std::sort (vertices.begin(), vertices.end(), [&](const Vertex& a, const Vertex& b) -> bool
-		{
-			if (renderer()->getFixedCamera (renderer()->camera()).negatedDepth)
-				return a[relZ] > b[relZ];
-
-			return a[relZ] < b[relZ];
-		});
-
-		for (const Vertex& vrt : vertices)
-		{
-			// If the vertex in 2d space is very close to the cursor then we use
-			// it regardless of depth.
-			QPoint vect2d = renderer()->coordconv3_2 (vrt) - cursorPosition2D;
-			const double distance2DSquared = std::pow (vect2d.x(), 2) + std::pow (vect2d.y(), 2);
-			if (distance2DSquared < 16.0 * 16.0)
-			{
-				closest = &vrt;
-				break;
-			}
-
-			// Check if too far away from the cursor.
-			if (distance2DSquared > 64.0 * 64.0)
-				continue;
-
-			// Not very close to the cursor. Compare using true distance,
-			// including depth.
-			const double distanceSquared = (vrt - cursorPosition).lengthSquared();
-
-			if (distanceSquared < minimumDistance)
-			{
-				minimumDistance = distanceSquared;
-				closest = &vrt;
-			}
-		}
-
-		if (closest != null)
-			addDrawnVertex (*closest);
-
-		return true;
-	}
-
-	if ((data.releasedButtons & Qt::RightButton) and (not m_drawedVerts.isEmpty()))
-	{
-		// Remove the last vertex
-		m_drawedVerts.removeLast();
-
-		return true;
-	}
-
-	return false;
-}
-
-void AbstractDrawMode::finishDraw (LDObjectList const& objs)
-{
-	int pos = g_win->getInsertionPoint();
-
-	if (objs.size() > 0)
-	{
-		for (LDObjectPtr obj : objs)
-		{
-			renderer()->document()->insertObj (pos++, obj);
-			renderer()->compileObject (obj);
-		}
-
-		g_win->refresh();
-		g_win->endAction();
-	}
-
-	m_drawedVerts.clear();
-}
-
-void AbstractDrawMode::drawLength (QPainter &painter, const Vertex &v0, const Vertex &v1,
-	const QPointF& v0p, const QPointF& v1p) const
-{
-	if (not cfg::DrawLineLengths)
-		return;
-
-	const QString label = QString::number ((v1 - v0).length());
-	QPoint origin = QLineF (v0p, v1p).pointAt (0.5).toPoint();
-	painter.drawText (origin, label);
-}
-
-void AbstractDrawMode::renderPolygon (QPainter& painter, const QVector<Vertex>& poly3d,
-	bool withlengths, bool withangles) const
-{
-	QVector<QPoint> poly (poly3d.size());
-	QFontMetrics metrics = QFontMetrics (QFont());
-
-	// Convert to 2D
-	for (int i = 0; i < poly3d.size(); ++i)
-		poly[i] = renderer()->coordconv3_2 (poly3d[i]);
-
-	// Draw the polygon-to-be
-	painter.setBrush (m_polybrush);
-	painter.drawPolygon (QPolygonF (poly));
-
-	// Draw vertex blips
-	for (int i = 0; i < poly3d.size(); ++i)
-	{
-		QPoint& blip = poly[i];
-		painter.setPen (renderer()->linePen());
-		renderer()->drawBlip (painter, blip);
-
-		// Draw their coordinates
-		painter.setPen (renderer()->textPen());
-		painter.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true));
-	}
-
-	// Draw line lenghts and angle info if appropriate
-	if (poly3d.size() >= 2 and (withlengths or withangles))
-	{
-		painter.setPen (renderer()->textPen());
-
-		for (int i = 0; i < poly3d.size(); ++i)
-		{
-			const int j = (i + 1) % poly3d.size();
-			const int h = (i - 1 >= 0) ? (i - 1) : (poly3d.size() - 1);
-
-			if (withlengths)
-				drawLength (painter, poly3d[i], poly3d[j], poly[i], poly[j]);
-
-			if (withangles and cfg::DrawAngles)
-			{
-				QLineF l0 (poly[h], poly[i]),
-					l1 (poly[i], poly[j]);
-
-				double angle = 180 - l0.angleTo (l1);
-
-				if (angle < 0)
-					angle = 180 - l1.angleTo (l0);
-
-				QString label = QString::number (angle) + QString::fromUtf8 (QByteArray ("\302\260"));
-				QPoint pos = poly[i];
-				pos.setY (pos.y() + metrics.height());
-
-				painter.drawText (pos, label);
-			}
-		}
-	}
-}
-
-bool AbstractDrawMode::keyReleased (QKeyEvent *ev)
-{
-	if (Super::keyReleased (ev))
-		return true;
-
-	if (not m_drawedVerts.isEmpty() and ev->key() == Qt::Key_Backspace)
-	{
-		m_drawedVerts.removeLast();
-		return true;
-	}
-
-	return false;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/abstractEditMode.cpp	Tue Mar 03 21:54:57 2015 +0200
@@ -0,0 +1,263 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QMouseEvent>
+#include <stdexcept>
+#include "abstractEditMode.h"
+#include "selectMode.h"
+#include "drawMode.h"
+#include "rectangleMode.h"
+#include "circleMode.h"
+#include "magicWandMode.h"
+#include "linePathMode.h"
+#include "../mainWindow.h"
+#include "../glRenderer.h"
+
+CFGENTRY (Bool, DrawLineLengths, true)
+CFGENTRY (Bool, DrawAngles, false)
+
+AbstractEditMode::AbstractEditMode (GLRenderer* renderer) :
+	m_renderer (renderer) {}
+
+AbstractEditMode::~AbstractEditMode() {}
+
+AbstractEditMode* AbstractEditMode::createByType (GLRenderer* renderer, EditModeType type)
+{
+	switch (type)
+	{
+		case EditModeType::Select: return new SelectMode (renderer);
+		case EditModeType::Draw: return new DrawMode (renderer);
+		case EditModeType::Rectangle: return new RectangleMode (renderer);
+		case EditModeType::Circle: return new CircleMode (renderer);
+		case EditModeType::MagicWand: return new MagicWandMode (renderer);
+		case EditModeType::LinePath: return new LinePathMode (renderer);
+	}
+
+	throw std::logic_error ("bad type given to AbstractEditMode::createByType");
+}
+
+GLRenderer* AbstractEditMode::renderer() const
+{
+	return m_renderer;
+}
+
+AbstractDrawMode::AbstractDrawMode (GLRenderer* renderer) :
+	AbstractEditMode (renderer),
+	m_polybrush (QBrush (QColor (64, 192, 0, 128)))
+{
+	// Disable the context menu - we need the right mouse button
+	// for removing vertices.
+	renderer->setContextMenuPolicy (Qt::NoContextMenu);
+
+	// Use the crosshair cursor when drawing.
+	renderer->setCursor (Qt::CrossCursor);
+
+	// Clear the selection when beginning to draw.
+	CurrentDocument()->clearSelection();
+
+	g_win->updateSelection();
+	m_drawedVerts.clear();
+}
+
+AbstractSelectMode::AbstractSelectMode (GLRenderer* renderer) :
+	AbstractEditMode (renderer)
+{
+	renderer->unsetCursor();
+	renderer->setContextMenuPolicy (Qt::DefaultContextMenu);
+}
+
+// =============================================================================
+//
+void AbstractDrawMode::addDrawnVertex (Vertex const& pos)
+{
+	if (preAddVertex (pos))
+		return;
+
+	m_drawedVerts << pos;
+}
+
+bool AbstractDrawMode::mouseReleased (MouseEventData const& data)
+{
+	if (Super::mouseReleased (data))
+		return true;
+
+	if ((data.releasedButtons & Qt::MidButton) and (m_drawedVerts.size() < 4) and (not data.mouseMoved))
+	{
+		// Find the closest vertex to our cursor
+		double			minimumDistance = 1024.0;
+		const Vertex*	closest = null;
+		Vertex			cursorPosition = renderer()->coordconv2_3 (data.ev->pos(), false);
+		QPoint			cursorPosition2D (data.ev->pos());
+		const Axis		relZ = renderer()->getRelativeZ();
+		QVector<Vertex>	vertices = renderer()->document()->inlineVertices();
+
+		// Sort the vertices in order of distance to camera
+		std::sort (vertices.begin(), vertices.end(), [&](const Vertex& a, const Vertex& b) -> bool
+		{
+			if (renderer()->getFixedCamera (renderer()->camera()).negatedDepth)
+				return a[relZ] > b[relZ];
+
+			return a[relZ] < b[relZ];
+		});
+
+		for (const Vertex& vrt : vertices)
+		{
+			// If the vertex in 2d space is very close to the cursor then we use
+			// it regardless of depth.
+			QPoint vect2d = renderer()->coordconv3_2 (vrt) - cursorPosition2D;
+			const double distance2DSquared = std::pow (vect2d.x(), 2) + std::pow (vect2d.y(), 2);
+			if (distance2DSquared < 16.0 * 16.0)
+			{
+				closest = &vrt;
+				break;
+			}
+
+			// Check if too far away from the cursor.
+			if (distance2DSquared > 64.0 * 64.0)
+				continue;
+
+			// Not very close to the cursor. Compare using true distance,
+			// including depth.
+			const double distanceSquared = (vrt - cursorPosition).lengthSquared();
+
+			if (distanceSquared < minimumDistance)
+			{
+				minimumDistance = distanceSquared;
+				closest = &vrt;
+			}
+		}
+
+		if (closest != null)
+			addDrawnVertex (*closest);
+
+		return true;
+	}
+
+	if ((data.releasedButtons & Qt::RightButton) and (not m_drawedVerts.isEmpty()))
+	{
+		// Remove the last vertex
+		m_drawedVerts.removeLast();
+
+		return true;
+	}
+
+	return false;
+}
+
+void AbstractDrawMode::finishDraw (LDObjectList const& objs)
+{
+	int pos = g_win->getInsertionPoint();
+
+	if (objs.size() > 0)
+	{
+		for (LDObjectPtr obj : objs)
+		{
+			renderer()->document()->insertObj (pos++, obj);
+			renderer()->compileObject (obj);
+		}
+
+		g_win->refresh();
+		g_win->endAction();
+	}
+
+	m_drawedVerts.clear();
+}
+
+void AbstractDrawMode::drawLength (QPainter &painter, const Vertex &v0, const Vertex &v1,
+	const QPointF& v0p, const QPointF& v1p) const
+{
+	if (not cfg::DrawLineLengths)
+		return;
+
+	const QString label = QString::number ((v1 - v0).length());
+	QPoint origin = QLineF (v0p, v1p).pointAt (0.5).toPoint();
+	painter.drawText (origin, label);
+}
+
+void AbstractDrawMode::renderPolygon (QPainter& painter, const QVector<Vertex>& poly3d,
+	bool withlengths, bool withangles) const
+{
+	QVector<QPoint> poly (poly3d.size());
+	QFontMetrics metrics = QFontMetrics (QFont());
+
+	// Convert to 2D
+	for (int i = 0; i < poly3d.size(); ++i)
+		poly[i] = renderer()->coordconv3_2 (poly3d[i]);
+
+	// Draw the polygon-to-be
+	painter.setBrush (m_polybrush);
+	painter.drawPolygon (QPolygonF (poly));
+
+	// Draw vertex blips
+	for (int i = 0; i < poly3d.size(); ++i)
+	{
+		QPoint& blip = poly[i];
+		painter.setPen (renderer()->linePen());
+		renderer()->drawBlip (painter, blip);
+
+		// Draw their coordinates
+		painter.setPen (renderer()->textPen());
+		painter.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true));
+	}
+
+	// Draw line lenghts and angle info if appropriate
+	if (poly3d.size() >= 2 and (withlengths or withangles))
+	{
+		painter.setPen (renderer()->textPen());
+
+		for (int i = 0; i < poly3d.size(); ++i)
+		{
+			const int j = (i + 1) % poly3d.size();
+			const int h = (i - 1 >= 0) ? (i - 1) : (poly3d.size() - 1);
+
+			if (withlengths)
+				drawLength (painter, poly3d[i], poly3d[j], poly[i], poly[j]);
+
+			if (withangles and cfg::DrawAngles)
+			{
+				QLineF l0 (poly[h], poly[i]),
+					l1 (poly[i], poly[j]);
+
+				double angle = 180 - l0.angleTo (l1);
+
+				if (angle < 0)
+					angle = 180 - l1.angleTo (l0);
+
+				QString label = QString::number (angle) + QString::fromUtf8 (QByteArray ("\302\260"));
+				QPoint pos = poly[i];
+				pos.setY (pos.y() + metrics.height());
+
+				painter.drawText (pos, label);
+			}
+		}
+	}
+}
+
+bool AbstractDrawMode::keyReleased (QKeyEvent *ev)
+{
+	if (Super::keyReleased (ev))
+		return true;
+
+	if (not m_drawedVerts.isEmpty() and ev->key() == Qt::Key_Backspace)
+	{
+		m_drawedVerts.removeLast();
+		return true;
+	}
+
+	return false;
+}
--- a/src/editmodes/circleMode.cc	Tue Mar 03 17:42:21 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,315 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QPainter>
-#include "circleMode.h"
-#include "../miscallenous.h"
-#include "../ldObject.h"
-#include "../ldDocument.h"
-#include "../ringFinder.h"
-#include "../primitives.h"
-#include "../glRenderer.h"
-#include "../mainWindow.h"
-#include "../ldObjectMath.h"
-
-CircleMode::CircleMode (GLRenderer* renderer) :
-	Super (renderer) {}
-
-EditModeType CircleMode::type() const
-{
-	return EditModeType::Circle;
-}
-
-double CircleMode::getCircleDrawDist (int pos) const
-{
-	assert (m_drawedVerts.size() >= pos + 1);
-	Vertex v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] :
-		renderer()->coordconv2_3 (renderer()->mousePosition(), false);
-	Axis localx, localy;
-	renderer()->getRelativeAxes (localx, localy);
-	double dx = m_drawedVerts[0][localx] - v1[localx];
-	double dy = m_drawedVerts[0][localy] - v1[localy];
-	return Grid::Snap (sqrt ((dx * dx) + (dy * dy)), Grid::Coordinate);
-}
-
-Matrix CircleMode::getCircleDrawMatrix (double scale)
-{
-	// Matrix templates. 2 is substituted with the scale value, 1 is inverted to -1 if needed.
-	static const Matrix templates[3] =
-	{
-		{ 2, 0, 0, 0, 1, 0, 0, 0, 2 },
-		{ 2, 0, 0, 0, 0, 2, 0, 1, 0 },
-		{ 0, 1, 0, 2, 0, 0, 0, 0, 2 },
-	};
-
-	Matrix transform = templates[renderer()->camera() % 3];
-
-	for (int i = 0; i < 9; ++i)
-	{
-		if (transform[i] == 2)
-			transform[i] = scale;
-		elif (transform[i] == 1 and renderer()->camera() >= 3)
-			transform[i] = -1;
-	}
-
-	return transform;
-}
-
-void CircleMode::buildCircle()
-{
-	LDObjectList objs;
-	const int segments (g_win->ringToolSegments());
-	const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution);
-	double dist0 (getCircleDrawDist (0));
-	double dist1 (getCircleDrawDist (1));
-	LDDocumentPtr refFile;
-	Matrix transform;
-	bool circleOrDisc = false;
-
-	if (dist1 < dist0)
-		qSwap (dist0, dist1);
-
-	if (dist0 == dist1)
-	{
-		// If the radii are the same, there's no ring space to fill. Use a circle.
-		refFile = GetPrimitive (::Circle, segments, divisions, 0);
-		transform = getCircleDrawMatrix (dist0);
-		circleOrDisc = true;
-	}
-	elif (dist0 == 0 or dist1 == 0)
-	{
-		// If either radii is 0, use a disc.
-		refFile = GetPrimitive (::Disc, segments, divisions, 0);
-		transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1);
-		circleOrDisc = true;
-	}
-	elif (g_RingFinder.findRings (dist0, dist1))
-	{
-		// The ring finder found a solution, use that. Add the component rings to the file.
-		for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents())
-		{
-			refFile = GetPrimitive (::Ring, segments, divisions, cmp.num);
-			LDSubfilePtr ref = LDSpawn<LDSubfile>();
-			ref->setFileInfo (refFile);
-			ref->setTransform (getCircleDrawMatrix (cmp.scale));
-			ref->setPosition (m_drawedVerts[0]);
-			ref->setColor (MainColor());
-			objs << ref;
-		}
-	}
-	else
-	{
-		// Ring finder failed, last resort: draw the ring with quads
-		QList<QLineF> c0, c1;
-		Axis localx, localy, localz;
-		renderer()->getRelativeAxes (localx, localy);
-		localz = (Axis) (3 - localx - localy);
-		double x0 (m_drawedVerts[0][localx]);
-		double y0 (m_drawedVerts[0][localy]);
-
-		Vertex templ;
-		templ.setCoordinate (localx, x0);
-		templ.setCoordinate (localy, y0);
-		templ.setCoordinate (localz, renderer()->getDepthValue());
-
-		// Calculate circle coords
-		MakeCircle (segments, divisions, dist0, c0);
-		MakeCircle (segments, divisions, dist1, c1);
-
-		for (int i = 0; i < segments; ++i)
-		{
-			Vertex v0, v1, v2, v3;
-			v0 = v1 = v2 = v3 = templ;
-			v0.setCoordinate (localx, v0[localx] + c0[i].x1());
-			v0.setCoordinate (localy, v0[localy] + c0[i].y1());
-			v1.setCoordinate (localx, v1[localx] + c0[i].x2());
-			v1.setCoordinate (localy, v1[localy] + c0[i].y2());
-			v2.setCoordinate (localx, v2[localx] + c1[i].x2());
-			v2.setCoordinate (localy, v2[localy] + c1[i].y2());
-			v3.setCoordinate (localx, v3[localx] + c1[i].x1());
-			v3.setCoordinate (localy, v3[localy] + c1[i].y1());
-
-			LDQuadPtr quad (LDSpawn<LDQuad> (v0, v1, v2, v3));
-			quad->setColor (MainColor());
-
-			// Ensure the quads always are BFC-front towards the camera
-			if (renderer()->camera() % 3 <= 0)
-				quad->invert();
-
-			objs << quad;
-		}
-	}
-
-	if (circleOrDisc and refFile != null)
-	{
-		LDSubfilePtr ref = LDSpawn<LDSubfile>();
-		ref->setFileInfo (refFile);
-		ref->setTransform (transform);
-		ref->setPosition (m_drawedVerts[0]);
-		ref->setColor (MainColor());
-		objs << ref;
-	}
-
-	unless (objs.isEmpty())
-	{
-		Axis relZ = renderer()->getRelativeZ();;
-		const int l (relZ == X ? 1 : 0);
-		const int m (relZ == Y ? 1 : 0);
-		const int n (relZ == Z ? 1 : 0);
-		RotateObjects (l, m, n, -m_angleOffset, objs);
-	}
-
-	finishDraw (objs);
-}
-
-double CircleMode::getAngleOffset() const
-{
-	if (m_drawedVerts.isEmpty())
-		return 0.0;
-
-	const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution);
-	QPointF originspot (renderer()->coordconv3_2 (m_drawedVerts.first()));
-	QLineF bearing (originspot, renderer()->mousePositionF());
-	QLineF bearing2 (originspot, QPointF (originspot.x(), 0.0));
-	double angleoffset (-bearing.angleTo (bearing2) + 90);
-	angleoffset /= (360.0 / divisions); // convert angle to 0-16 scale
-	angleoffset = round (angleoffset); // round to nearest 16th
-	angleoffset *= ((2 * Pi) / divisions); // convert to radians
-	angleoffset *= renderer()->depthNegateFactor(); // negate based on camera
-	return angleoffset;
-}
-
-void CircleMode::render (QPainter& painter) const
-{
-	QFontMetrics metrics = QFontMetrics (QFont());
-
-	// If we have not specified the center point of the circle yet, preview it on the screen.
-	if (m_drawedVerts.isEmpty())
-	{
-		renderer()->drawBlip (painter, renderer()->coordconv3_2 (renderer()->position3D()));
-		return;
-	}
-
-	QVector<Vertex> innerverts, outerverts;
-	QVector<QPointF> innerverts2d, outerverts2d;
-	const double innerdistance (getCircleDrawDist (0));
-	const double outerdistance (m_drawedVerts.size() >= 2 ? getCircleDrawDist (1) : -1);
-	const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution);
-	const int segments (g_win->ringToolSegments());
-	const double angleUnit (2 * Pi / divisions);
-	Axis relX, relY;
-	renderer()->getRelativeAxes (relX, relY);
-	const double angleoffset (m_drawedVerts.size() < 3 ? getAngleOffset() : m_angleOffset);
-
-	// Calculate the preview positions of vertices
-	for (int i = 0; i < segments + 1; ++i)
-	{
-		const double sinangle (sin (angleoffset + i * angleUnit));
-		const double cosangle (cos (angleoffset + i * angleUnit));
-		Vertex v (Origin);
-		v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * innerdistance));
-		v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * innerdistance));
-		innerverts << v;
-		innerverts2d << renderer()->coordconv3_2 (v);
-
-		if (outerdistance != -1)
-		{
-			v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * outerdistance));
-			v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * outerdistance));
-			outerverts << v;
-			outerverts2d << renderer()->coordconv3_2 (v);
-		}
-	}
-
-	QVector<QLineF> lines (segments);
-
-	if (outerdistance != -1 and outerdistance != innerdistance)
-	{
-		painter.setBrush (m_polybrush);
-		painter.setPen (Qt::NoPen);
-
-		// Compile polygons
-		for (int i = 0; i < segments; ++i)
-		{
-			QVector<QPointF> points;
-			points << innerverts2d[i]
-				<< innerverts2d[i + 1]
-				<< outerverts2d[i + 1]
-				<< outerverts2d[i];
-			painter.drawPolygon (QPolygonF (points));
-			lines << QLineF (innerverts2d[i], innerverts2d[i + 1]);
-			lines << QLineF (outerverts2d[i], outerverts2d[i + 1]);
-		}
-
-		// Add bordering edges for unclosed rings/discs
-		if (segments != divisions)
-		{
-			lines << QLineF (innerverts2d.first(), outerverts2d.first());
-			lines << QLineF (innerverts2d.last(), outerverts2d.last());
-		}
-	}
-	else
-	{
-		for (int i = 0; i < segments; ++i)
-			lines << QLineF (innerverts2d[i], innerverts2d[i + 1]);
-	}
-
-	// Draw a green blips at where the points are
-	for (QPointF const& point : innerverts2d + outerverts2d)
-		renderer()->drawBlip (painter, point);
-
-	// Draw edge lines
-	painter.setPen (renderer()->linePen());
-	painter.drawLines (lines);
-
-	// Draw the current radius in the middle of the circle.
-	QPoint origin = renderer()->coordconv3_2 (m_drawedVerts[0]);
-	QString label = QString::number (innerdistance);
-	painter.setPen (renderer()->textPen());
-	painter.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label);
-
-	if (m_drawedVerts.size() >= 2)
-	{
-		painter.drawText (origin.x() - (metrics.width (label) / 2),
-			origin.y() + metrics.height(), QString::number (outerdistance));
-	}
-}
-
-bool CircleMode::mouseReleased (MouseEventData const& data)
-{
-	if (Super::mouseReleased (data))
-		return true;
-
-	if (data.releasedButtons & Qt::LeftButton)
-	{
-		if (m_drawedVerts.size() < 3)
-			addDrawnVertex (renderer()->position3D());
-		else
-			buildCircle();
-
-		return true;
-	}
-
-	return false;
-}
-
-bool CircleMode::preAddVertex (const Vertex&)
-{
-	m_angleOffset = getAngleOffset();
-	return false;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/circleMode.cpp	Tue Mar 03 21:54:57 2015 +0200
@@ -0,0 +1,315 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QPainter>
+#include "circleMode.h"
+#include "../miscallenous.h"
+#include "../ldObject.h"
+#include "../ldDocument.h"
+#include "../ringFinder.h"
+#include "../primitives.h"
+#include "../glRenderer.h"
+#include "../mainWindow.h"
+#include "../ldObjectMath.h"
+
+CircleMode::CircleMode (GLRenderer* renderer) :
+	Super (renderer) {}
+
+EditModeType CircleMode::type() const
+{
+	return EditModeType::Circle;
+}
+
+double CircleMode::getCircleDrawDist (int pos) const
+{
+	assert (m_drawedVerts.size() >= pos + 1);
+	Vertex v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] :
+		renderer()->coordconv2_3 (renderer()->mousePosition(), false);
+	Axis localx, localy;
+	renderer()->getRelativeAxes (localx, localy);
+	double dx = m_drawedVerts[0][localx] - v1[localx];
+	double dy = m_drawedVerts[0][localy] - v1[localy];
+	return Grid::Snap (sqrt ((dx * dx) + (dy * dy)), Grid::Coordinate);
+}
+
+Matrix CircleMode::getCircleDrawMatrix (double scale)
+{
+	// Matrix templates. 2 is substituted with the scale value, 1 is inverted to -1 if needed.
+	static const Matrix templates[3] =
+	{
+		{ 2, 0, 0, 0, 1, 0, 0, 0, 2 },
+		{ 2, 0, 0, 0, 0, 2, 0, 1, 0 },
+		{ 0, 1, 0, 2, 0, 0, 0, 0, 2 },
+	};
+
+	Matrix transform = templates[renderer()->camera() % 3];
+
+	for (int i = 0; i < 9; ++i)
+	{
+		if (transform[i] == 2)
+			transform[i] = scale;
+		elif (transform[i] == 1 and renderer()->camera() >= 3)
+			transform[i] = -1;
+	}
+
+	return transform;
+}
+
+void CircleMode::buildCircle()
+{
+	LDObjectList objs;
+	const int segments (g_win->ringToolSegments());
+	const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution);
+	double dist0 (getCircleDrawDist (0));
+	double dist1 (getCircleDrawDist (1));
+	LDDocumentPtr refFile;
+	Matrix transform;
+	bool circleOrDisc = false;
+
+	if (dist1 < dist0)
+		qSwap (dist0, dist1);
+
+	if (dist0 == dist1)
+	{
+		// If the radii are the same, there's no ring space to fill. Use a circle.
+		refFile = GetPrimitive (::Circle, segments, divisions, 0);
+		transform = getCircleDrawMatrix (dist0);
+		circleOrDisc = true;
+	}
+	elif (dist0 == 0 or dist1 == 0)
+	{
+		// If either radii is 0, use a disc.
+		refFile = GetPrimitive (::Disc, segments, divisions, 0);
+		transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1);
+		circleOrDisc = true;
+	}
+	elif (g_RingFinder.findRings (dist0, dist1))
+	{
+		// The ring finder found a solution, use that. Add the component rings to the file.
+		for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents())
+		{
+			refFile = GetPrimitive (::Ring, segments, divisions, cmp.num);
+			LDSubfilePtr ref = LDSpawn<LDSubfile>();
+			ref->setFileInfo (refFile);
+			ref->setTransform (getCircleDrawMatrix (cmp.scale));
+			ref->setPosition (m_drawedVerts[0]);
+			ref->setColor (MainColor());
+			objs << ref;
+		}
+	}
+	else
+	{
+		// Ring finder failed, last resort: draw the ring with quads
+		QList<QLineF> c0, c1;
+		Axis localx, localy, localz;
+		renderer()->getRelativeAxes (localx, localy);
+		localz = (Axis) (3 - localx - localy);
+		double x0 (m_drawedVerts[0][localx]);
+		double y0 (m_drawedVerts[0][localy]);
+
+		Vertex templ;
+		templ.setCoordinate (localx, x0);
+		templ.setCoordinate (localy, y0);
+		templ.setCoordinate (localz, renderer()->getDepthValue());
+
+		// Calculate circle coords
+		MakeCircle (segments, divisions, dist0, c0);
+		MakeCircle (segments, divisions, dist1, c1);
+
+		for (int i = 0; i < segments; ++i)
+		{
+			Vertex v0, v1, v2, v3;
+			v0 = v1 = v2 = v3 = templ;
+			v0.setCoordinate (localx, v0[localx] + c0[i].x1());
+			v0.setCoordinate (localy, v0[localy] + c0[i].y1());
+			v1.setCoordinate (localx, v1[localx] + c0[i].x2());
+			v1.setCoordinate (localy, v1[localy] + c0[i].y2());
+			v2.setCoordinate (localx, v2[localx] + c1[i].x2());
+			v2.setCoordinate (localy, v2[localy] + c1[i].y2());
+			v3.setCoordinate (localx, v3[localx] + c1[i].x1());
+			v3.setCoordinate (localy, v3[localy] + c1[i].y1());
+
+			LDQuadPtr quad (LDSpawn<LDQuad> (v0, v1, v2, v3));
+			quad->setColor (MainColor());
+
+			// Ensure the quads always are BFC-front towards the camera
+			if (renderer()->camera() % 3 <= 0)
+				quad->invert();
+
+			objs << quad;
+		}
+	}
+
+	if (circleOrDisc and refFile != null)
+	{
+		LDSubfilePtr ref = LDSpawn<LDSubfile>();
+		ref->setFileInfo (refFile);
+		ref->setTransform (transform);
+		ref->setPosition (m_drawedVerts[0]);
+		ref->setColor (MainColor());
+		objs << ref;
+	}
+
+	unless (objs.isEmpty())
+	{
+		Axis relZ = renderer()->getRelativeZ();;
+		const int l (relZ == X ? 1 : 0);
+		const int m (relZ == Y ? 1 : 0);
+		const int n (relZ == Z ? 1 : 0);
+		RotateObjects (l, m, n, -m_angleOffset, objs);
+	}
+
+	finishDraw (objs);
+}
+
+double CircleMode::getAngleOffset() const
+{
+	if (m_drawedVerts.isEmpty())
+		return 0.0;
+
+	const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution);
+	QPointF originspot (renderer()->coordconv3_2 (m_drawedVerts.first()));
+	QLineF bearing (originspot, renderer()->mousePositionF());
+	QLineF bearing2 (originspot, QPointF (originspot.x(), 0.0));
+	double angleoffset (-bearing.angleTo (bearing2) + 90);
+	angleoffset /= (360.0 / divisions); // convert angle to 0-16 scale
+	angleoffset = round (angleoffset); // round to nearest 16th
+	angleoffset *= ((2 * Pi) / divisions); // convert to radians
+	angleoffset *= renderer()->depthNegateFactor(); // negate based on camera
+	return angleoffset;
+}
+
+void CircleMode::render (QPainter& painter) const
+{
+	QFontMetrics metrics = QFontMetrics (QFont());
+
+	// If we have not specified the center point of the circle yet, preview it on the screen.
+	if (m_drawedVerts.isEmpty())
+	{
+		renderer()->drawBlip (painter, renderer()->coordconv3_2 (renderer()->position3D()));
+		return;
+	}
+
+	QVector<Vertex> innerverts, outerverts;
+	QVector<QPointF> innerverts2d, outerverts2d;
+	const double innerdistance (getCircleDrawDist (0));
+	const double outerdistance (m_drawedVerts.size() >= 2 ? getCircleDrawDist (1) : -1);
+	const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution);
+	const int segments (g_win->ringToolSegments());
+	const double angleUnit (2 * Pi / divisions);
+	Axis relX, relY;
+	renderer()->getRelativeAxes (relX, relY);
+	const double angleoffset (m_drawedVerts.size() < 3 ? getAngleOffset() : m_angleOffset);
+
+	// Calculate the preview positions of vertices
+	for (int i = 0; i < segments + 1; ++i)
+	{
+		const double sinangle (sin (angleoffset + i * angleUnit));
+		const double cosangle (cos (angleoffset + i * angleUnit));
+		Vertex v (Origin);
+		v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * innerdistance));
+		v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * innerdistance));
+		innerverts << v;
+		innerverts2d << renderer()->coordconv3_2 (v);
+
+		if (outerdistance != -1)
+		{
+			v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * outerdistance));
+			v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * outerdistance));
+			outerverts << v;
+			outerverts2d << renderer()->coordconv3_2 (v);
+		}
+	}
+
+	QVector<QLineF> lines (segments);
+
+	if (outerdistance != -1 and outerdistance != innerdistance)
+	{
+		painter.setBrush (m_polybrush);
+		painter.setPen (Qt::NoPen);
+
+		// Compile polygons
+		for (int i = 0; i < segments; ++i)
+		{
+			QVector<QPointF> points;
+			points << innerverts2d[i]
+				<< innerverts2d[i + 1]
+				<< outerverts2d[i + 1]
+				<< outerverts2d[i];
+			painter.drawPolygon (QPolygonF (points));
+			lines << QLineF (innerverts2d[i], innerverts2d[i + 1]);
+			lines << QLineF (outerverts2d[i], outerverts2d[i + 1]);
+		}
+
+		// Add bordering edges for unclosed rings/discs
+		if (segments != divisions)
+		{
+			lines << QLineF (innerverts2d.first(), outerverts2d.first());
+			lines << QLineF (innerverts2d.last(), outerverts2d.last());
+		}
+	}
+	else
+	{
+		for (int i = 0; i < segments; ++i)
+			lines << QLineF (innerverts2d[i], innerverts2d[i + 1]);
+	}
+
+	// Draw a green blips at where the points are
+	for (QPointF const& point : innerverts2d + outerverts2d)
+		renderer()->drawBlip (painter, point);
+
+	// Draw edge lines
+	painter.setPen (renderer()->linePen());
+	painter.drawLines (lines);
+
+	// Draw the current radius in the middle of the circle.
+	QPoint origin = renderer()->coordconv3_2 (m_drawedVerts[0]);
+	QString label = QString::number (innerdistance);
+	painter.setPen (renderer()->textPen());
+	painter.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label);
+
+	if (m_drawedVerts.size() >= 2)
+	{
+		painter.drawText (origin.x() - (metrics.width (label) / 2),
+			origin.y() + metrics.height(), QString::number (outerdistance));
+	}
+}
+
+bool CircleMode::mouseReleased (MouseEventData const& data)
+{
+	if (Super::mouseReleased (data))
+		return true;
+
+	if (data.releasedButtons & Qt::LeftButton)
+	{
+		if (m_drawedVerts.size() < 3)
+			addDrawnVertex (renderer()->position3D());
+		else
+			buildCircle();
+
+		return true;
+	}
+
+	return false;
+}
+
+bool CircleMode::preAddVertex (const Vertex&)
+{
+	m_angleOffset = getAngleOffset();
+	return false;
+}
--- a/src/editmodes/drawMode.cc	Tue Mar 03 17:42:21 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QPainter>
-#include <QMouseEvent>
-#include "drawMode.h"
-#include "../ldObject.h"
-#include "../glRenderer.h"
-#include "../miscallenous.h"
-
-DrawMode::DrawMode (GLRenderer* renderer) :
-	Super (renderer) {}
-
-EditModeType DrawMode::type() const
-{
-	return EditModeType::Draw;
-}
-
-void DrawMode::render (QPainter& painter) const
-{
-	QVector<Vertex> poly;
-	QFontMetrics metrics = QFontMetrics (QFont());
-
-	for (Vertex const& vert : m_drawedVerts)
-		poly << vert;
-
-	// Draw the cursor vertex as the last one in the list.
-	if (poly.size() < 4)
-		poly << getCursorVertex();
-
-	renderPolygon (painter, poly, true, true);
-}
-
-bool DrawMode::preAddVertex (Vertex const& pos)
-{
-	// If we picked an already-existing vertex, stop drawing
-	for (Vertex& vert : m_drawedVerts)
-	{
-		if (vert == pos)
-		{
-			endDraw();
-			return true;
-		}
-	}
-
-	return false;
-}
-
-bool DrawMode::mouseReleased (MouseEventData const& data)
-{
-	if (Super::mouseReleased (data))
-		return true;
-
-	if (data.releasedButtons & Qt::LeftButton)
-	{
-		// If we have 4 verts, stop drawing.
-		if (m_drawedVerts.size() >= 4)
-		{
-			endDraw();
-			return true;
-		}
-
-		addDrawnVertex (getCursorVertex());
-		return true;
-	}
-
-	return false;
-}
-
-void DrawMode::endDraw()
-{
-	// Clean the selection and create the object
-	QList<Vertex>& verts = m_drawedVerts;
-	LDObjectList objs;
-
-	switch (verts.size())
-	{
-		case 1:
-			return;
-
-		case 2:
-		{
-			// 2 verts - make a line
-			LDLinePtr obj = LDSpawn<LDLine> (verts[0], verts[1]);
-			obj->setColor (EdgeColor());
-			objs << obj;
-			break;
-		}
-
-		case 3:
-		case 4:
-		{
-			LDObjectPtr obj = (verts.size() == 3) ?
-				static_cast<LDObjectPtr> (LDSpawn<LDTriangle>()) :
-				static_cast<LDObjectPtr> (LDSpawn<LDQuad>());
-
-			obj->setColor (MainColor());
-
-			for (int i = 0; i < verts.size(); ++i)
-				obj->setVertex (i, verts[i]);
-
-			objs << obj;
-			break;
-		}
-	}
-
-	finishDraw (objs);
-}
-
-template<typename _Type>
-_Type IntervalClamp (_Type a, _Type interval)
-{
-	_Type remainder = a % interval;
-
-	if (remainder >= float (interval / 2))
-		a += interval;
-
-	a -= remainder;
-	return a;
-}
-
-Vertex DrawMode::getCursorVertex() const
-{
-	Vertex result = renderer()->position3D();
-
-	if (renderer()->keyboardModifiers() & Qt::ControlModifier
-		and not m_drawedVerts.isEmpty())
-	{
-		Vertex const& v0 = m_drawedVerts.last();
-		Vertex const& v1 = result;
-		Axis relX, relY;
-
-		renderer()->getRelativeAxes (relX, relY);
-		QLineF ln (v0[relX], v0[relY], v1[relX], v1[relY]);
-		ln.setAngle (IntervalClamp<int> (ln.angle(), 45));
-		result.setCoordinate (relX, Grid::Snap (ln.x2(), Grid::Coordinate));
-		result.setCoordinate (relY, Grid::Snap (ln.y2(), Grid::Coordinate));
-	}
-
-	return result;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/drawMode.cpp	Tue Mar 03 21:54:57 2015 +0200
@@ -0,0 +1,156 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QPainter>
+#include <QMouseEvent>
+#include "drawMode.h"
+#include "../ldObject.h"
+#include "../glRenderer.h"
+#include "../miscallenous.h"
+
+DrawMode::DrawMode (GLRenderer* renderer) :
+	Super (renderer) {}
+
+EditModeType DrawMode::type() const
+{
+	return EditModeType::Draw;
+}
+
+void DrawMode::render (QPainter& painter) const
+{
+	QVector<Vertex> poly;
+	QFontMetrics metrics = QFontMetrics (QFont());
+
+	for (Vertex const& vert : m_drawedVerts)
+		poly << vert;
+
+	// Draw the cursor vertex as the last one in the list.
+	if (poly.size() < 4)
+		poly << getCursorVertex();
+
+	renderPolygon (painter, poly, true, true);
+}
+
+bool DrawMode::preAddVertex (Vertex const& pos)
+{
+	// If we picked an already-existing vertex, stop drawing
+	for (Vertex& vert : m_drawedVerts)
+	{
+		if (vert == pos)
+		{
+			endDraw();
+			return true;
+		}
+	}
+
+	return false;
+}
+
+bool DrawMode::mouseReleased (MouseEventData const& data)
+{
+	if (Super::mouseReleased (data))
+		return true;
+
+	if (data.releasedButtons & Qt::LeftButton)
+	{
+		// If we have 4 verts, stop drawing.
+		if (m_drawedVerts.size() >= 4)
+		{
+			endDraw();
+			return true;
+		}
+
+		addDrawnVertex (getCursorVertex());
+		return true;
+	}
+
+	return false;
+}
+
+void DrawMode::endDraw()
+{
+	// Clean the selection and create the object
+	QList<Vertex>& verts = m_drawedVerts;
+	LDObjectList objs;
+
+	switch (verts.size())
+	{
+		case 1:
+			return;
+
+		case 2:
+		{
+			// 2 verts - make a line
+			LDLinePtr obj = LDSpawn<LDLine> (verts[0], verts[1]);
+			obj->setColor (EdgeColor());
+			objs << obj;
+			break;
+		}
+
+		case 3:
+		case 4:
+		{
+			LDObjectPtr obj = (verts.size() == 3) ?
+				static_cast<LDObjectPtr> (LDSpawn<LDTriangle>()) :
+				static_cast<LDObjectPtr> (LDSpawn<LDQuad>());
+
+			obj->setColor (MainColor());
+
+			for (int i = 0; i < verts.size(); ++i)
+				obj->setVertex (i, verts[i]);
+
+			objs << obj;
+			break;
+		}
+	}
+
+	finishDraw (objs);
+}
+
+template<typename _Type>
+_Type IntervalClamp (_Type a, _Type interval)
+{
+	_Type remainder = a % interval;
+
+	if (remainder >= float (interval / 2))
+		a += interval;
+
+	a -= remainder;
+	return a;
+}
+
+Vertex DrawMode::getCursorVertex() const
+{
+	Vertex result = renderer()->position3D();
+
+	if (renderer()->keyboardModifiers() & Qt::ControlModifier
+		and not m_drawedVerts.isEmpty())
+	{
+		Vertex const& v0 = m_drawedVerts.last();
+		Vertex const& v1 = result;
+		Axis relX, relY;
+
+		renderer()->getRelativeAxes (relX, relY);
+		QLineF ln (v0[relX], v0[relY], v1[relX], v1[relY]);
+		ln.setAngle (IntervalClamp<int> (ln.angle(), 45));
+		result.setCoordinate (relX, Grid::Snap (ln.x2(), Grid::Coordinate));
+		result.setCoordinate (relY, Grid::Snap (ln.y2(), Grid::Coordinate));
+	}
+
+	return result;
+}
--- a/src/editmodes/magicWandMode.cc	Tue Mar 03 17:42:21 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,220 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QMouseEvent>
-#include "magicWandMode.h"
-#include "../ldDocument.h"
-#include "../mainWindow.h"
-#include "../glRenderer.h"
-
-MagicWandMode::MagicWandMode (GLRenderer* renderer) :
-	Super (renderer)
-{
-	// Get vertex<->object data
-	for (LDObjectPtr obj : CurrentDocument()->objects())
-	{
-		// Note: this deliberately only takes vertex-objects into account.
-		// The magic wand does not process subparts.
-		for (int i = 0; i < obj->numVertices(); ++i)
-			m_vertices[obj->vertex (i)] << obj;
-	}
-}
-
-EditModeType MagicWandMode::type() const
-{
-	return EditModeType::MagicWand;
-}
-
-void MagicWandMode::fillBoundaries (LDObjectPtr obj, QVector<BoundaryType>& boundaries, QVector<LDObjectPtr>& candidates)
-{
-	// All boundaries obviously share vertices with the object, therefore they're all in the list
-	// of candidates.
-	for (auto it = candidates.begin(); it != candidates.end(); ++it)
-	{
-		if (not Eq ((*it)->type(), OBJ_Line, OBJ_CondLine) or (*it)->vertex (0) == (*it)->vertex (1))
-			continue;
-
-		int matches = 0;
-
-		for (int i = 0; i < obj->numVertices(); ++i)
-		{
-			if (not Eq (obj->vertex (i), (*it)->vertex (0), (*it)->vertex (1)))
-				continue;
-
-			if (++matches == 2)
-			{
-				// Boundary found. If it's an edgeline, add it to the boundaries list, if a
-				// conditional line, select it.
-				if ((*it)->type() == OBJ_CondLine)
-					m_selection << *it;
-				else
-					boundaries.append (std::make_tuple ((*it)->vertex (0), (*it)->vertex (1)));
-
-				break;
-			}
-		}
-	}
-}
-
-void MagicWandMode::doMagic (LDObjectPtr obj, MagicWandMode::MagicType type)
-{
-	if (obj == null)
-	{
-		if (type == Set)
-		{
-			CurrentDocument()->clearSelection();
-			g_win->buildObjList();
-		}
-
-		return;
-	}
-
-	int matchesneeded = 0;
-	QVector<BoundaryType> boundaries;
-	LDObjectType objtype = obj->type();
-
-	if (type != InternalRecursion)
-	{
-		m_selection.clear();
-		m_selection.append (obj);
-	}
-
-	switch (obj->type())
-	{
-		case OBJ_Line:
-		case OBJ_CondLine:
-			matchesneeded = 1;
-			break;
-
-		case OBJ_Triangle:
-		case OBJ_Quad:
-			matchesneeded = 2;
-			break;
-
-		default:
-			return;
-	}
-
-	QVector<LDObjectPtr> candidates;
-
-	// Get the list of objects that touch this object, i.e. share a vertex
-	// with this.
-	for (int i = 0; i < obj->numVertices(); ++i)
-		candidates += m_vertices[obj->vertex (i)];
-
-	RemoveDuplicates (candidates);
-
-	// If we're dealing with surfaces, get a list of boundaries.
-	if (matchesneeded > 1)
-		fillBoundaries (obj, boundaries, candidates);
-
-	for (LDObjectPtr candidate : candidates)
-	{
-		try
-		{
-			// If we're doing this on lines, we need exact type match. Surface types (quads and
-			// triangles) can be mixed. Also don't consider self a candidate, and don't consider
-			// objects we have already processed.
-			if ((candidate == obj) or
-				(candidate->color() != obj->color()) or
-				(m_selection.contains (candidate)) or
-				(matchesneeded == 1 and (candidate->type() != objtype)) or
-				((candidate->numVertices() > 2) ^ (matchesneeded == 2)))
-			{
-				throw 0;
-			}
-
-			// Now ensure the two objects share enough vertices.
-			QVector<Vertex> matches;
-
-			for (int i = 0; i < obj->numVertices(); ++i)
-			{
-				for (int j = 0; j < candidate->numVertices(); ++j)
-				{
-					if (obj->vertex(i) == candidate->vertex(j))
-					{
-						matches << obj->vertex(i);
-						break;
-					}
-				}
-			}
-
-			if (matches.size() < matchesneeded)
-				throw 0; // Not enough matches.
-
-			// Check if a boundary gets in between the objects.
-			for (auto boundary : boundaries)
-			{
-				if (Eq (matches[0], std::get<0> (boundary), std::get<1> (boundary)) and
-					Eq (matches[1], std::get<0> (boundary), std::get<1> (boundary)))
-				{
-					throw 0;
-				}
-			}
-
-			m_selection.append (candidate);
-			doMagic (candidate, InternalRecursion);
-		}
-		catch (int&)
-		{
-			continue;
-		}
-	}
-
-	switch (type)
-	{
-		case Set:
-			CurrentDocument()->clearSelection();
-		case Additive:
-			for (LDObjectPtr obj : m_selection)
-				obj->select();
-			break;
-
-		case Subtractive:
-			for (LDObjectPtr obj : m_selection)
-				obj->deselect();
-			break;
-
-		case InternalRecursion:
-			break;
-	}
-
-	if (type != InternalRecursion)
-		g_win->buildObjList();
-}
-
-bool MagicWandMode::mouseReleased (MouseEventData const& data)
-{
-	if (Super::mouseReleased (data))
-		return true;
-
-	if (data.releasedButtons & Qt::LeftButton and not data.mouseMoved)
-	{
-		MagicType wandtype = MagicWandMode::Set;
-
-		if (data.keymods & Qt::ShiftModifier)
-			wandtype = MagicWandMode::Additive;
-		elif (data.keymods & Qt::ControlModifier)
-			wandtype = MagicWandMode::Subtractive;
-
-		doMagic (renderer()->pickOneObject (data.ev->x(), data.ev->y()), wandtype);
-		return true;
-	}
-
-	return false;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/magicWandMode.cpp	Tue Mar 03 21:54:57 2015 +0200
@@ -0,0 +1,220 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QMouseEvent>
+#include "magicWandMode.h"
+#include "../ldDocument.h"
+#include "../mainWindow.h"
+#include "../glRenderer.h"
+
+MagicWandMode::MagicWandMode (GLRenderer* renderer) :
+	Super (renderer)
+{
+	// Get vertex<->object data
+	for (LDObjectPtr obj : CurrentDocument()->objects())
+	{
+		// Note: this deliberately only takes vertex-objects into account.
+		// The magic wand does not process subparts.
+		for (int i = 0; i < obj->numVertices(); ++i)
+			m_vertices[obj->vertex (i)] << obj;
+	}
+}
+
+EditModeType MagicWandMode::type() const
+{
+	return EditModeType::MagicWand;
+}
+
+void MagicWandMode::fillBoundaries (LDObjectPtr obj, QVector<BoundaryType>& boundaries, QVector<LDObjectPtr>& candidates)
+{
+	// All boundaries obviously share vertices with the object, therefore they're all in the list
+	// of candidates.
+	for (auto it = candidates.begin(); it != candidates.end(); ++it)
+	{
+		if (not Eq ((*it)->type(), OBJ_Line, OBJ_CondLine) or (*it)->vertex (0) == (*it)->vertex (1))
+			continue;
+
+		int matches = 0;
+
+		for (int i = 0; i < obj->numVertices(); ++i)
+		{
+			if (not Eq (obj->vertex (i), (*it)->vertex (0), (*it)->vertex (1)))
+				continue;
+
+			if (++matches == 2)
+			{
+				// Boundary found. If it's an edgeline, add it to the boundaries list, if a
+				// conditional line, select it.
+				if ((*it)->type() == OBJ_CondLine)
+					m_selection << *it;
+				else
+					boundaries.append (std::make_tuple ((*it)->vertex (0), (*it)->vertex (1)));
+
+				break;
+			}
+		}
+	}
+}
+
+void MagicWandMode::doMagic (LDObjectPtr obj, MagicWandMode::MagicType type)
+{
+	if (obj == null)
+	{
+		if (type == Set)
+		{
+			CurrentDocument()->clearSelection();
+			g_win->buildObjList();
+		}
+
+		return;
+	}
+
+	int matchesneeded = 0;
+	QVector<BoundaryType> boundaries;
+	LDObjectType objtype = obj->type();
+
+	if (type != InternalRecursion)
+	{
+		m_selection.clear();
+		m_selection.append (obj);
+	}
+
+	switch (obj->type())
+	{
+		case OBJ_Line:
+		case OBJ_CondLine:
+			matchesneeded = 1;
+			break;
+
+		case OBJ_Triangle:
+		case OBJ_Quad:
+			matchesneeded = 2;
+			break;
+
+		default:
+			return;
+	}
+
+	QVector<LDObjectPtr> candidates;
+
+	// Get the list of objects that touch this object, i.e. share a vertex
+	// with this.
+	for (int i = 0; i < obj->numVertices(); ++i)
+		candidates += m_vertices[obj->vertex (i)];
+
+	RemoveDuplicates (candidates);
+
+	// If we're dealing with surfaces, get a list of boundaries.
+	if (matchesneeded > 1)
+		fillBoundaries (obj, boundaries, candidates);
+
+	for (LDObjectPtr candidate : candidates)
+	{
+		try
+		{
+			// If we're doing this on lines, we need exact type match. Surface types (quads and
+			// triangles) can be mixed. Also don't consider self a candidate, and don't consider
+			// objects we have already processed.
+			if ((candidate == obj) or
+				(candidate->color() != obj->color()) or
+				(m_selection.contains (candidate)) or
+				(matchesneeded == 1 and (candidate->type() != objtype)) or
+				((candidate->numVertices() > 2) ^ (matchesneeded == 2)))
+			{
+				throw 0;
+			}
+
+			// Now ensure the two objects share enough vertices.
+			QVector<Vertex> matches;
+
+			for (int i = 0; i < obj->numVertices(); ++i)
+			{
+				for (int j = 0; j < candidate->numVertices(); ++j)
+				{
+					if (obj->vertex(i) == candidate->vertex(j))
+					{
+						matches << obj->vertex(i);
+						break;
+					}
+				}
+			}
+
+			if (matches.size() < matchesneeded)
+				throw 0; // Not enough matches.
+
+			// Check if a boundary gets in between the objects.
+			for (auto boundary : boundaries)
+			{
+				if (Eq (matches[0], std::get<0> (boundary), std::get<1> (boundary)) and
+					Eq (matches[1], std::get<0> (boundary), std::get<1> (boundary)))
+				{
+					throw 0;
+				}
+			}
+
+			m_selection.append (candidate);
+			doMagic (candidate, InternalRecursion);
+		}
+		catch (int&)
+		{
+			continue;
+		}
+	}
+
+	switch (type)
+	{
+		case Set:
+			CurrentDocument()->clearSelection();
+		case Additive:
+			for (LDObjectPtr obj : m_selection)
+				obj->select();
+			break;
+
+		case Subtractive:
+			for (LDObjectPtr obj : m_selection)
+				obj->deselect();
+			break;
+
+		case InternalRecursion:
+			break;
+	}
+
+	if (type != InternalRecursion)
+		g_win->buildObjList();
+}
+
+bool MagicWandMode::mouseReleased (MouseEventData const& data)
+{
+	if (Super::mouseReleased (data))
+		return true;
+
+	if (data.releasedButtons & Qt::LeftButton and not data.mouseMoved)
+	{
+		MagicType wandtype = MagicWandMode::Set;
+
+		if (data.keymods & Qt::ShiftModifier)
+			wandtype = MagicWandMode::Additive;
+		elif (data.keymods & Qt::ControlModifier)
+			wandtype = MagicWandMode::Subtractive;
+
+		doMagic (renderer()->pickOneObject (data.ev->x(), data.ev->y()), wandtype);
+		return true;
+	}
+
+	return false;
+}
--- a/src/editmodes/rectangleMode.cc	Tue Mar 03 17:42:21 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QPainter>
-#include <QMouseEvent>
-#include "rectangleMode.h"
-#include "../ldObject.h"
-#include "../glRenderer.h"
-
-RectangleMode::RectangleMode (GLRenderer* renderer) :
-	Super (renderer),
-	m_rectangleVerts (QVector<Vertex>(4)) {}
-
-EditModeType RectangleMode::type() const
-{
-	return EditModeType::Rectangle;
-}
-
-void RectangleMode::render (QPainter& painter) const
-{
-	renderPolygon (painter, (m_drawedVerts.size() > 0) ? m_rectangleVerts :
-		QVector<Vertex> ({renderer()->position3D()}), true, false);
-}
-
-bool RectangleMode::mouseReleased (MouseEventData const& data)
-{
-	if (Super::mouseReleased (data))
-		return true;
-
-	if (data.releasedButtons & Qt::LeftButton)
-	{
-		if (m_drawedVerts.size() == 2)
-		{
-			LDQuadPtr quad (LDSpawn<LDQuad>());
-			updateRectVerts();
-
-			for (int i = 0; i < quad->numVertices(); ++i)
-				quad->setVertex (i, m_rectangleVerts[i]);
-
-			quad->setColor (MainColor());
-			finishDraw (LDObjectList ({quad}));
-			return true;
-		}
-
-		addDrawnVertex (renderer()->position3D());
-		return true;
-	}
-
-	return false;
-}
-
-//
-// Update rect vertices when the mouse moves since the 3d position likely has changed
-//
-bool RectangleMode::mouseMoved (QMouseEvent*)
-{
-	updateRectVerts();
-	return false;
-}
-
-void RectangleMode::updateRectVerts()
-{
-	if (m_drawedVerts.isEmpty())
-	{
-		for (int i = 0; i < 4; ++i)
-			m_rectangleVerts[i] = renderer()->position3D();
-
-		return;
-	}
-
-	Vertex v0 = m_drawedVerts[0],
-		   v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : renderer()->position3D();
-
-	const Axis localx = renderer()->getCameraAxis (false),
-			   localy = renderer()->getCameraAxis (true),
-			   localz = (Axis) (3 - localx - localy);
-
-	for (int i = 0; i < 4; ++i)
-		m_rectangleVerts[i].setCoordinate (localz, renderer()->getDepthValue());
-
-	m_rectangleVerts[0].setCoordinate (localx, v0[localx]);
-	m_rectangleVerts[0].setCoordinate (localy, v0[localy]);
-	m_rectangleVerts[1].setCoordinate (localx, v1[localx]);
-	m_rectangleVerts[1].setCoordinate (localy, v0[localy]);
-	m_rectangleVerts[2].setCoordinate (localx, v1[localx]);
-	m_rectangleVerts[2].setCoordinate (localy, v1[localy]);
-	m_rectangleVerts[3].setCoordinate (localx, v0[localx]);
-	m_rectangleVerts[3].setCoordinate (localy, v1[localy]);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/rectangleMode.cpp	Tue Mar 03 21:54:57 2015 +0200
@@ -0,0 +1,104 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QPainter>
+#include <QMouseEvent>
+#include "rectangleMode.h"
+#include "../ldObject.h"
+#include "../glRenderer.h"
+
+RectangleMode::RectangleMode (GLRenderer* renderer) :
+	Super (renderer),
+	m_rectangleVerts (QVector<Vertex>(4)) {}
+
+EditModeType RectangleMode::type() const
+{
+	return EditModeType::Rectangle;
+}
+
+void RectangleMode::render (QPainter& painter) const
+{
+	renderPolygon (painter, (m_drawedVerts.size() > 0) ? m_rectangleVerts :
+		QVector<Vertex> ({renderer()->position3D()}), true, false);
+}
+
+bool RectangleMode::mouseReleased (MouseEventData const& data)
+{
+	if (Super::mouseReleased (data))
+		return true;
+
+	if (data.releasedButtons & Qt::LeftButton)
+	{
+		if (m_drawedVerts.size() == 2)
+		{
+			LDQuadPtr quad (LDSpawn<LDQuad>());
+			updateRectVerts();
+
+			for (int i = 0; i < quad->numVertices(); ++i)
+				quad->setVertex (i, m_rectangleVerts[i]);
+
+			quad->setColor (MainColor());
+			finishDraw (LDObjectList ({quad}));
+			return true;
+		}
+
+		addDrawnVertex (renderer()->position3D());
+		return true;
+	}
+
+	return false;
+}
+
+//
+// Update rect vertices when the mouse moves since the 3d position likely has changed
+//
+bool RectangleMode::mouseMoved (QMouseEvent*)
+{
+	updateRectVerts();
+	return false;
+}
+
+void RectangleMode::updateRectVerts()
+{
+	if (m_drawedVerts.isEmpty())
+	{
+		for (int i = 0; i < 4; ++i)
+			m_rectangleVerts[i] = renderer()->position3D();
+
+		return;
+	}
+
+	Vertex v0 = m_drawedVerts[0],
+		   v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : renderer()->position3D();
+
+	const Axis localx = renderer()->getCameraAxis (false),
+			   localy = renderer()->getCameraAxis (true),
+			   localz = (Axis) (3 - localx - localy);
+
+	for (int i = 0; i < 4; ++i)
+		m_rectangleVerts[i].setCoordinate (localz, renderer()->getDepthValue());
+
+	m_rectangleVerts[0].setCoordinate (localx, v0[localx]);
+	m_rectangleVerts[0].setCoordinate (localy, v0[localy]);
+	m_rectangleVerts[1].setCoordinate (localx, v1[localx]);
+	m_rectangleVerts[1].setCoordinate (localy, v0[localy]);
+	m_rectangleVerts[2].setCoordinate (localx, v1[localx]);
+	m_rectangleVerts[2].setCoordinate (localy, v1[localy]);
+	m_rectangleVerts[3].setCoordinate (localx, v0[localx]);
+	m_rectangleVerts[3].setCoordinate (localy, v1[localy]);
+}
--- a/src/editmodes/selectMode.cc	Tue Mar 03 17:42:21 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,137 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2015 Teemu Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QMouseEvent>
-#include "selectMode.h"
-#include "../glRenderer.h"
-#include "../addObjectDialog.h"
-#include "../mainWindow.h"
-#include "../glRenderer.h"
-
-SelectMode::SelectMode (GLRenderer* renderer) :
-	Super (renderer),
-	m_rangepick (false) {}
-
-EditModeType SelectMode::type() const
-{
-	return EditModeType::Select;
-}
-
-void SelectMode::render (QPainter& painter) const
-{
-	// If we're range-picking, draw a rectangle encompassing the selection area.
-	if (m_rangepick)
-	{
-		int x0 = m_rangeStart.x(),
-			y0 = m_rangeStart.y(),
-			x1 = renderer()->mousePosition().x(),
-			y1 = renderer()->mousePosition().y();
-
-		QRect rect (x0, y0, x1 - x0, y1 - y0);
-		QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF");
-		fillColor.setAlphaF (0.2f);
-		painter.setPen (QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
-		painter.setBrush (QBrush (fillColor));
-		painter.drawRect (rect);
-	}
-}
-
-bool SelectMode::mouseReleased (MouseEventData const& data)
-{
-	if (Super::mouseReleased (data))
-		return true;
-
-	if (data.releasedButtons & Qt::LeftButton)
-	{
-		if (not data.mouseMoved)
-			m_rangepick = false;
-
-		if (not m_rangepick)
-			m_addpick = (data.keymods & Qt::ControlModifier);
-
-		if (not data.mouseMoved or m_rangepick)
-		{
-			QRect area;
-			int const mx = data.ev->x();
-			int const my = data.ev->y();
-
-			if (not m_rangepick)
-			{
-				area = QRect (mx, my, 1, 1);
-			}
-			else
-			{
-				int const x = Min (m_rangeStart.x(), mx);
-				int const y = Min (m_rangeStart.y(), my);
-				int const width = Abs (m_rangeStart.x() - mx);
-				int const height = Abs (m_rangeStart.y() - my);
-				area = QRect (x, y, width, height);
-			}
-
-			renderer()->pick (area, m_addpick);
-		}
-
-		m_rangepick = false;
-		return true;
-	}
-
-	return false;
-}
-
-bool SelectMode::mousePressed (QMouseEvent* ev)
-{
-	if (Super::mousePressed (ev))
-		return true;
-
-	if (ev->modifiers() & Qt::ControlModifier)
-	{
-		m_rangepick = true;
-		m_rangeStart.setX (ev->x());
-		m_rangeStart.setY (ev->y());
-		m_addpick = (ev->modifiers() & Qt::AltModifier);
-		return true;
-	}
-
-	return false;
-}
-
-bool SelectMode::mouseDoubleClicked (QMouseEvent* ev)
-{
-	if (Super::mouseDoubleClicked (ev))
-		return true;
-
-	if (ev->buttons() & Qt::LeftButton)
-	{
-		renderer()->document()->clearSelection();
-		LDObjectPtr obj = renderer()->pickOneObject (ev->x(), ev->y());
-
-		if (obj != null)
-		{
-			AddObjectDialog::staticDialog (obj->type(), obj);
-			g_win->endAction();
-			return true;
-		}
-	}
-
-	return false;
-}
-
-bool SelectMode::mouseMoved (QMouseEvent*)
-{
-	return m_rangepick;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/selectMode.cpp	Tue Mar 03 21:54:57 2015 +0200
@@ -0,0 +1,137 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2015 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QMouseEvent>
+#include "selectMode.h"
+#include "../glRenderer.h"
+#include "../addObjectDialog.h"
+#include "../mainWindow.h"
+#include "../glRenderer.h"
+
+SelectMode::SelectMode (GLRenderer* renderer) :
+	Super (renderer),
+	m_rangepick (false) {}
+
+EditModeType SelectMode::type() const
+{
+	return EditModeType::Select;
+}
+
+void SelectMode::render (QPainter& painter) const
+{
+	// If we're range-picking, draw a rectangle encompassing the selection area.
+	if (m_rangepick)
+	{
+		int x0 = m_rangeStart.x(),
+			y0 = m_rangeStart.y(),
+			x1 = renderer()->mousePosition().x(),
+			y1 = renderer()->mousePosition().y();
+
+		QRect rect (x0, y0, x1 - x0, y1 - y0);
+		QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF");
+		fillColor.setAlphaF (0.2f);
+		painter.setPen (QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
+		painter.setBrush (QBrush (fillColor));
+		painter.drawRect (rect);
+	}
+}
+
+bool SelectMode::mouseReleased (MouseEventData const& data)
+{
+	if (Super::mouseReleased (data))
+		return true;
+
+	if (data.releasedButtons & Qt::LeftButton)
+	{
+		if (not data.mouseMoved)
+			m_rangepick = false;
+
+		if (not m_rangepick)
+			m_addpick = (data.keymods & Qt::ControlModifier);
+
+		if (not data.mouseMoved or m_rangepick)
+		{
+			QRect area;
+			int const mx = data.ev->x();
+			int const my = data.ev->y();
+
+			if (not m_rangepick)
+			{
+				area = QRect (mx, my, 1, 1);
+			}
+			else
+			{
+				int const x = Min (m_rangeStart.x(), mx);
+				int const y = Min (m_rangeStart.y(), my);
+				int const width = Abs (m_rangeStart.x() - mx);
+				int const height = Abs (m_rangeStart.y() - my);
+				area = QRect (x, y, width, height);
+			}
+
+			renderer()->pick (area, m_addpick);
+		}
+
+		m_rangepick = false;
+		return true;
+	}
+
+	return false;
+}
+
+bool SelectMode::mousePressed (QMouseEvent* ev)
+{
+	if (Super::mousePressed (ev))
+		return true;
+
+	if (ev->modifiers() & Qt::ControlModifier)
+	{
+		m_rangepick = true;
+		m_rangeStart.setX (ev->x());
+		m_rangeStart.setY (ev->y());
+		m_addpick = (ev->modifiers() & Qt::AltModifier);
+		return true;
+	}
+
+	return false;
+}
+
+bool SelectMode::mouseDoubleClicked (QMouseEvent* ev)
+{
+	if (Super::mouseDoubleClicked (ev))
+		return true;
+
+	if (ev->buttons() & Qt::LeftButton)
+	{
+		renderer()->document()->clearSelection();
+		LDObjectPtr obj = renderer()->pickOneObject (ev->x(), ev->y());
+
+		if (obj != null)
+		{
+			AddObjectDialog::staticDialog (obj->type(), obj);
+			g_win->endAction();
+			return true;
+		}
+	}
+
+	return false;
+}
+
+bool SelectMode::mouseMoved (QMouseEvent*)
+{
+	return m_rangepick;
+}
--- a/updaterevision.py	Tue Mar 03 17:42:21 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-#
-#	Copyright 2014 Teemu Piippo
-#	All rights reserved.
-#
-#	Redistribution and use in source and binary forms, with or without
-#	modification, are permitted provided that the following conditions
-#	are met:
-#
-#	1. Redistributions of source code must retain the above copyright
-#	   notice, this list of conditions and the following disclaimer.
-#	2. Redistributions in binary form must reproduce the above copyright
-#	   notice, this list of conditions and the following disclaimer in the
-#	   documentation and/or other materials provided with the distribution.
-#	3. Neither the name of the copyright holder nor the names of its
-#	   contributors may be used to endorse or promote products derived from
-#	   this software without specific prior written permission.
-#
-#	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-#	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
-#	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-#	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
-#	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-#	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-#	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-#	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-#	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-#	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-#	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-
-import sys
-import subprocess
-from datetime import datetime
-
-if len (sys.argv) != 2:
-    print 'usage: %s <output>' % sys.argv[0]
-    quit (1)
-
-oldrev = ''
-
-try:
-    with open (sys.argv[1]) as fp:
-        oldrev = fp.readline().replace ('\n', '').replace ('// ', '')
-except IOError:
-    pass
-
-data = subprocess.check_output (['hg', 'log', '-r.', '--template',
-    '{node|short} {branch} {date|hgdate}']).replace ('\n', '').split (' ')
-
-rev = data[0]
-branch = data[1]
-timestamp = int (data[2])
-date = datetime.utcfromtimestamp (timestamp)
-datestring = date.strftime ('%y%m%d-%H%M') if date.year >= 2000 else '000000-0000'
-
-if len(rev) > 7:
-    rev = rev[0:7]
-
-if subprocess.check_output (['hg', 'id', '-n']).replace ('\n', '')[-1] == '+':
-    rev += '+'
-
-if rev == oldrev:
-    print "%s is up to date at %s" % (sys.argv[1], rev)
-    quit (0)
-
-with open (sys.argv[1], 'w') as fp:
-    fp.write ('// %s\n' % rev)
-    fp.write ('#define HG_NODE "%s"\n' % rev)
-    fp.write ('#define HG_BRANCH "%s"\n' % branch)
-    fp.write ('#define HG_DATE_VERSION "%s"\n' % datestring)
-    fp.write ('#define HG_DATE_STRING "%s"\n' % date.strftime ('%d %b %Y'))
-    fp.write ('#define HG_DATE_TIME %d\n' % int (timestamp))
-    print '%s updated to %s' % (sys.argv[1], rev)

mercurial