- saving work done on edit mode revamp

Fri, 04 Jul 2014 22:19:01 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Fri, 04 Jul 2014 22:19:01 +0300
changeset 823
1a2f593f0c02
parent 821
a67b1201942a
child 824
6add2126e7ff

- saving work done on edit mode revamp

src/editmodes/abstracteditmode.cc file | annotate | diff | comparison | revisions
src/editmodes/abstracteditmode.h file | annotate | diff | comparison | revisions
src/editmodes/circlemode.cc file | annotate | diff | comparison | revisions
src/editmodes/circlemode.h file | annotate | diff | comparison | revisions
src/editmodes/drawmode.cc file | annotate | diff | comparison | revisions
src/editmodes/drawmode.h file | annotate | diff | comparison | revisions
src/editmodes/magicwandmode.cc file | annotate | diff | comparison | revisions
src/editmodes/magicwandmode.h file | annotate | diff | comparison | revisions
src/editmodes/selectmode.cc file | annotate | diff | comparison | revisions
src/editmodes/selectmode.h file | annotate | diff | comparison | revisions
src/glRenderer.cc file | annotate | diff | comparison | revisions
src/glRenderer.h file | annotate | diff | comparison | revisions
src/ldDocument.cc file | annotate | diff | comparison | revisions
src/macros.h file | annotate | diff | comparison | revisions
src/magicWand.cc file | annotate | diff | comparison | revisions
src/magicWand.h file | annotate | diff | comparison | revisions
src/mainWindow.cc file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/abstracteditmode.cc	Fri Jul 04 22:19:01 2014 +0300
@@ -0,0 +1,123 @@
+#include <stdexcept>
+#include "abstracteditmode.h"
+#include "selectmode.h"
+#include "drawmode.h"
+#include "circlemode.h"
+#include "magicwandmode.h"
+#include "../mainWindow.h"
+
+AbstractEditMode::AbstractEditMode (GLRenderer* renderer) :
+	_renderer (renderer) {}
+
+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::Circle: return new CircleMode (renderer);
+		case EditModeType::MagicWand: return new MagicWandMode (renderer);
+	}
+
+	throw std::logic_error ("bad type given to AbstractEditMode::createByType");
+}
+
+GLRenderer* AbstractEditMode::renderer() const
+{
+	return _renderer;
+}
+
+AbstractDrawMode::AbstractDrawMode (GLRenderer* renderer) :
+	AbstractEditMode (renderer),
+	_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.
+	getCurrentDocument()->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;
+}
+
+virtual void AbstractDrawMode::mouseReleased (MouseEventData const& data)
+{
+	if (data.releasedButtons & Qt::MidButton)
+	{
+		// 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();
+		QList<Vertex>	vertices;
+
+		for (auto it = renderer()->document()->vertices().begin(); it != renderer()->document()->vertices().end(); ++it)
+			vertices << it.key();
+
+		// 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;
+	}
+
+	return false;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/abstracteditmode.h	Fri Jul 04 22:19:01 2014 +0300
@@ -0,0 +1,79 @@
+#pragma once
+#include "../main.h"
+#include "../glRenderer.h"
+
+class QPainter;
+class GLRenderer;
+
+enum class EditModeType
+{
+	Select,
+	Draw,
+	Circle,
+	MagicWand,
+};
+
+class AbstractEditMode
+{
+	GLRenderer* _renderer;
+	QBrush		_polybrush;
+
+public:
+	struct MouseEventData
+	{
+		QMouseEvent* ev;
+		Qt::KeyboardModifiers keymods;
+		bool mouseMoved;
+		Qt::MouseButtons releasedButtons;
+	};
+
+	AbstractEditMode (GLRenderer* renderer);
+
+	virtual bool			allowFreeCamera() const = 0;
+	virtual void			render (QPainter& painter) const {};
+	GLRenderer*				renderer() const;
+	virtual EditModeType	type() const = 0;
+	virtual void			mousePressed (MouseEventData const& data) {}
+	virtual void			mouseReleased (MouseEventData const& data) {}
+
+	static AbstractEditMode* createByType (GLRenderer* renderer, EditModeType type);
+};
+
+//
+// Base class for draw-like edit modes
+//
+class AbstractDrawMode : public AbstractEditMode
+{
+	QList<Vertex>			m_drawedVerts;
+	Vertex					m_rectverts[4];
+
+public:
+	AbstractDrawMode (GLRenderer* renderer);
+
+	virtual bool allowFreeCamera() const override
+	{
+		return false;
+	}
+
+	virtual void mouseReleased (MouseEventData const& data) override;
+	void addDrawnVertex (const Vertex& pos);
+
+	virtual bool preAddVertex (Vertex const&)
+	{
+		return false;
+	}
+};
+
+//
+// Base class for select-like edit modes
+//
+class AbstractSelectMode : public AbstractEditMode
+{
+public:
+	AbstractSelectMode (GLRenderer* renderer);
+
+	virtual bool allowFreeCamera() const override
+	{
+		return true;
+	}
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/circlemode.cc	Fri Jul 04 22:19:01 2014 +0300
@@ -0,0 +1,133 @@
+#include <QPainter>
+#include "circlemode.h"
+#include "../miscallenous.h"
+#include "../ldObject.h"
+
+CircleMode::CircleMode (GLRenderer* renderer) :
+	Super (renderer) {}
+
+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 && renderer()->camera() >= 3)
+			transform[i] = -1;
+	}
+
+	return transform;
+}
+
+void CircleMode::render (QPainter& painter) const
+{
+	// 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()));
+	}
+	else
+	{
+		QVector<Vertex> verts, verts2;
+		const double dist0 = getCircleDrawDist (0),
+			dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1;
+		const int segs = g_lores;
+		const double angleUnit = (2 * pi) / segs;
+		Axis relX, relY;
+		QVector<QPoint> ringpoints, circlepoints, circle2points;
+
+		renderer()->getRelativeAxes (relX, relY);
+
+		// Calculate the preview positions of vertices
+		for (int i = 0; i < segs; ++i)
+		{
+			Vertex v = g_origin;
+			v.setCoordinate (relX, m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0));
+			v.setCoordinate (relY, m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0));
+			verts << v;
+
+			if (dist1 != -1)
+			{
+				v.setCoordinate (relX, m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1));
+				v.setCoordinate (relY, m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist1));
+				verts2 << v;
+			}
+		}
+
+		int i = 0;
+		for (const Vertex& v : verts + verts2)
+		{
+			// Calculate the 2D point of the vertex
+			QPoint point = renderer()->coordconv3_2 (v);
+
+			// Draw a green blip at where it is
+			renderer()->drawBlip (painter, point);
+
+			// Add it to the list of points for the green ring fill.
+			ringpoints << point;
+
+			// Also add the circle points to separate lists
+			if (i < verts.size())
+				circlepoints << point;
+			else
+				circle2points << point;
+
+			++i;
+		}
+
+		// Insert the first point as the seventeenth one so that
+		// the ring polygon is closed properly.
+		if (ringpoints.size() >= 16)
+			ringpoints.insert (16, ringpoints[0]);
+
+		// Same for the outer ring. Note that the indices are offset by 1
+		// because of the insertion done above bumps the values.
+		if (ringpoints.size() >= 33)
+			ringpoints.insert (33, ringpoints[17]);
+
+		// Draw the ring
+		painter.setBrush ((m_drawedVerts.size() >= 2) ? _polybrush : Qt::NoBrush);
+		painter.setPen (Qt::NoPen);
+		painter.drawPolygon (QPolygon (ringpoints));
+
+		// Draw the circles
+		painter.setBrush (Qt::NoBrush);
+		painter.setPen (renderer()->getLinePen());
+		painter.drawPolygon (QPolygon (circlepoints));
+		painter.drawPolygon (QPolygon (circle2points));
+
+		// Draw the current radius in the middle of the circle.
+		QPoint origin = renderer()->coordconv3_2 (m_drawedVerts[0]);
+		QString label = QString::number (dist0);
+		painter.setPen (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 (dist1));
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/circlemode.h	Fri Jul 04 22:19:01 2014 +0300
@@ -0,0 +1,15 @@
+#include "abstracteditmode.h"
+
+class CircleMode : public AbstractDrawMode
+{
+	DEFINE_CLASS (CircleMode, AbstractDrawMode)
+
+public:
+	CircleMode (GLRenderer* renderer);
+
+	virtual void render (QPainter& painter) const override;
+	virtual EditModeType type() const override;
+
+	double					getCircleDrawDist (int pos) const;
+	Matrix					getCircleDrawMatrix (double scale);
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/drawmode.cc	Fri Jul 04 22:19:01 2014 +0300
@@ -0,0 +1,151 @@
+#include <QPainter>
+#include "drawmode.h"
+
+CFGENTRY (Bool,		drawLineLengths,			true)
+CFGENTRY (Bool,		drawAngles,					false)
+
+DrawMode::DrawMode (GLRenderer* renderer) :
+	Super (renderer),
+	_rectdraw (false) {}
+
+EditModeType DrawMode::type() const
+{
+	return EditModeType::Draw;
+}
+
+void DrawMode::render (QPainter& painter) const
+{
+	QPoint poly[4];
+	Vertex poly3d[4];
+	int numverts = 4;
+	QFontMetrics metrics (QFont());
+
+	// Calculate polygon data
+	if (not _rectdraw)
+	{
+		numverts = m_drawedVerts.size() + 1;
+		int i = 0;
+
+		for (Vertex& vert : m_drawedVerts)
+			poly3d[i++] = vert;
+
+		// Draw the cursor vertex as the last one in the list.
+		if (numverts <= 4)
+			poly3d[i] = renderer()->position3D();
+		else
+			numverts = 4;
+	}
+	else
+	{
+		// Get vertex information from m_rectverts
+		if (m_drawedVerts.size() > 0)
+			for (int i = 0; i < numverts; ++i)
+				poly3d[i] = m_rectverts[i];
+		else
+			poly3d[0] = renderer()->position3D();
+	}
+
+	// Convert to 2D
+	for (int i = 0; i < numverts; ++i)
+		poly[i] = renderer()->coordconv3_2 (poly3d[i]);
+
+	if (numverts > 0)
+	{
+		// Draw the polygon-to-be
+		painter.setBrush (_polybrush);
+		painter.drawPolygon (poly, numverts);
+
+		// Draw vertex blips
+		for (int i = 0; i < numverts; ++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 (numverts >= 2)
+		{
+			int numlines = (m_drawedVerts.size() == 1) ? 1 : m_drawedVerts.size() + 1;
+			painter.setPen (renderer()->textPen());
+
+			for (int i = 0; i < numlines; ++i)
+			{
+				const int j = (i + 1 < numverts) ? i + 1 : 0;
+				const int h = (i - 1 >= 0) ? i - 1 : numverts - 1;
+
+				if (cfg::drawLineLengths)
+				{
+					const QString label = QString::number ((poly3d[j] - poly3d[i]).length());
+					QPoint origin = QLineF (poly[i], poly[j]).pointAt (0.5).toPoint();
+					painter.drawText (origin, label);
+				}
+
+				if (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 DrawMode::preAddVertex (Vertex const& pos)
+{
+	// If we picked an already-existing vertex, stop drawing
+	for (Vertex& vert : m_drawedVerts)
+	{
+		if (vert == pos)
+		{
+			endDraw (true);
+			return true;
+		}
+	}
+
+	return false;
+}
+
+void DrawMode::mouseReleased (MouseEventData const& data)
+{
+	if (_rectdraw)
+	{
+		if (m_drawedVerts.size() == 2)
+		{
+			endDraw (true);
+			return;
+		}
+	}
+	else
+	{
+		// If we have 4 verts, stop drawing.
+		if (m_drawedVerts.size() >= 4)
+		{
+			endDraw (true);
+			return;
+		}
+
+		if (m_drawedVerts.isEmpty() && ev->modifiers() & Qt::ShiftModifier)
+		{
+			_rectdraw = true;
+			updateRectVerts();
+		}
+	}
+
+	addDrawnVertex (renderer()->position3D());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/drawmode.h	Fri Jul 04 22:19:01 2014 +0300
@@ -0,0 +1,15 @@
+#include "abstracteditmode.h"
+
+class DrawMode : public AbstractDrawMode
+{
+	DEFINE_CLASS (DrawMode, AbstractDrawMode)
+	bool _rectdraw;
+
+public:
+	DrawMode (GLRenderer* renderer);
+
+	virtual void render (QPainter& painter) const override;
+	virtual EditModeType type() const override;
+	virtual bool preAddVertex (Vertex const& pos) override;
+	virtual void mouseReleased (MouseEventData const& data) override;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/magicwandmode.cc	Fri Jul 04 22:19:01 2014 +0300
@@ -0,0 +1,210 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QMouseEvent>
+#include "magicwandmode.h"
+#include "../ldDocument.h"
+#include "../mainWindow.h"
+
+MagicWandMode::MagicWandMode (GLRenderer* renderer) :
+	Super (renderer)
+{
+	// Get vertex<->object data
+	for (LDObjectPtr obj : getCurrentDocument()->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)
+			_vertices[obj->vertex (i)] << obj;
+	}
+}
+
+EditModeType MagicWandMode::type() const
+{
+	return EditModeType::MagicWand;
+}
+
+bool MagicWandMode::allowFreeCamera() const
+{
+	return true;
+}
+
+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 ((*it)->type() != OBJ_Line || (*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. Add to boundaries list and get it off the candidates list.
+				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)
+		{
+			getCurrentDocument()->clearSelection();
+			g_win->buildObjList();
+		}
+
+		return;
+	}
+
+	int matchesneeded = 0;
+	QVector<BoundaryType> boundaries;
+	LDObjectType objtype = obj->type();
+
+	if (type != InternalRecursion)
+	{
+		_selection.clear();
+		_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 += _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) ||
+				(candidate->color() != obj->color()) ||
+				(_selection.contains (candidate)) ||
+				(matchesneeded == 1 && (candidate->type() != objtype)) ||
+				((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)) &&
+					eq (matches[1], std::get<0> (boundary), std::get<1> (boundary)))
+				{
+					throw 0;
+				}
+			}
+
+			_selection.append (candidate);
+			doMagic (candidate, InternalRecursion);
+		}
+		catch (int&)
+		{
+			continue;
+		}
+	}
+
+	switch (type)
+	{
+		case Set:
+			getCurrentDocument()->clearSelection();
+		case Additive:
+			for (LDObjectPtr obj : _selection)
+				obj->select();
+			break;
+
+		case Subtractive:
+			for (LDObjectPtr obj : _selection)
+				obj->deselect();
+			break;
+
+		case InternalRecursion:
+			break;
+	}
+
+	if (type != InternalRecursion)
+		g_win->buildObjList();
+}
+
+void MagicWandMode::mouseReleased (MouseEventData const& data)
+{
+	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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/magicwandmode.h	Fri Jul 04 22:19:01 2014 +0300
@@ -0,0 +1,49 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include "abstracteditmode.h"
+#include "../basics.h"
+#include <QMap>
+#include <QVector>
+
+class MagicWandMode : public AbstractSelectMode
+{
+	QMap<Vertex, QVector<LDObjectPtr>> _vertices;
+	QVector<LDObjectPtr> _selection;
+
+	DEFINE_CLASS (MagicWandMode, AbstractSelectMode)
+
+public:
+	using BoundaryType = std::tuple<Vertex, Vertex>;
+	enum MagicType
+	{
+		Set,
+		Additive,
+		Subtractive,
+		InternalRecursion
+	};
+
+	MagicWandMode (GLRenderer* renderer);
+	void doMagic (LDObjectPtr obj, MagicType type);
+	virtual EditModeType type() const override;
+	virtual void mouseReleased (MouseEventData const& data) override;
+
+private:
+	void fillBoundaries (LDObjectPtr obj, QVector<BoundaryType>& boundaries, QVector<LDObjectPtr>& candidates);
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/selectmode.cc	Fri Jul 04 22:19:01 2014 +0300
@@ -0,0 +1,35 @@
+#include <QMouseEvent>
+#include "../glRenderer.h"
+#include "selectmode.h"
+
+SelectMode::SelectMode (GLRenderer* renderer) :
+	Super (renderer) {}
+
+EditModeType SelectMode::type() const
+{
+	return EditModeType::Select;
+}
+
+void SelectMode::mouseReleased (MouseEventData const& data)
+{
+	if (not data.mouseMoved)
+		_rangepick = false;
+
+	if (not _rangepick)
+		_addpick = (data.keymods & Qt::ControlModifier);
+
+	if (not data.mouseMoved || _rangepick)
+		renderer()->pick (data.ev->x(), data.ev->y());
+}
+
+void SelectMode::mousePressed (MouseEventData const& data)
+{
+	if (data.ev->modifiers() & Qt::ControlModifier)
+	{
+		_rangepick = true;
+		_rangeStart.setX (data.ev->x());
+		_rangeStart.setY (data.ev->y());
+		_addpick = (data.keymods & Qt::AltModifier);
+		data.ev->accept();
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/editmodes/selectmode.h	Fri Jul 04 22:19:01 2014 +0300
@@ -0,0 +1,16 @@
+#include "abstracteditmode.h"
+
+class SelectMode : public AbstractSelectMode
+{
+	QPoint _rangeStart;
+	bool _rangepick;
+	bool _addpick;
+
+	DEFINE_CLASS (SelectMode, AbstractSelectMode)
+
+public:
+	SelectMode (GLRenderer* renderer);
+
+	virtual void mouseReleased (MouseEventData const& data) override;
+	virtual EditModeType type() const override;
+};
\ No newline at end of file
--- a/src/glRenderer.cc	Fri Jul 04 00:09:37 2014 +0300
+++ b/src/glRenderer.cc	Fri Jul 04 22:19:01 2014 +0300
@@ -55,15 +55,6 @@
 	{{  0, -1, 0 }, Z, Y, false,  true, true }, // right
 };
 
-// Matrix templates for circle drawing. 2 is substituted with
-// the scale value, 1 is inverted to -1 if needed.
-static const Matrix g_circleDrawMatrixTemplates[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 },
-};
-
 CFGENTRY (String,	backgroundColor,			"#FFFFFF")
 CFGENTRY (String,	mainColor,					"#A0A0A0")
 CFGENTRY (Float,	mainColorAlpha,				1.0)
@@ -75,8 +66,6 @@
 CFGENTRY (Bool,		drawWireframe,				false)
 CFGENTRY (Bool,		useLogoStuds,				false)
 CFGENTRY (Bool,		antiAliasedLines,			true)
-CFGENTRY (Bool,		drawLineLengths,			true)
-CFGENTRY (Bool,		drawAngles,					false)
 CFGENTRY (Bool,		randomColors,				false)
 CFGENTRY (Bool,		highlightObjectBelowCursor,	true)
 CFGENTRY (Bool,		drawSurfaces,				true)
@@ -116,11 +105,10 @@
 //
 GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent)
 {
-	m_isPicking = m_rangepick = false;
+	m_isPicking = false;
 	m_camera = (ECamera) cfg::camera;
 	m_drawToolTip = false;
-	m_editMode = ESelectMode;
-	m_rectdraw = false;
+	m_editmode = AbstractEditMode::createByType (EditModeType::Select);
 	m_panning = false;
 	m_compiler = new GLCompiler (this);
 	setDrawOnly (false);
@@ -133,7 +121,6 @@
 	m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
 	m_thinBorderPen = m_thickBorderPen;
 	m_thinBorderPen.setWidth (1);
-	m_wand = null;
 	setAcceptDrops (true);
 	connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer()));
 
@@ -523,7 +510,7 @@
 	assert (camera() != EFreeCamera);
 
 	Vertex pos3d;
-	const LDFixedCameraInfo* cam = &g_FixedCameras[camera()];
+	const LDFixedCamera* cam = &g_FixedCameras[camera()];
 	const Axis axisX = cam->axisX;
 	const Axis axisY = cam->axisY;
 	const int negXFac = cam->negX ? -1 : 1,
@@ -560,7 +547,7 @@
 QPoint GLRenderer::coordconv3_2 (const Vertex& pos3d) const
 {
 	GLfloat m[16];
-	const LDFixedCameraInfo* cam = &g_FixedCameras[camera()];
+	const LDFixedCamera* cam = &g_FixedCameras[camera()];
 	const Axis axisX = cam->axisX;
 	const Axis axisY = cam->axisY;
 	const int negXFac = cam->negX ? -1 : 1,
@@ -583,6 +570,19 @@
 	return QPoint (rx, -ry);
 }
 
+QPen GLRenderer::textPen() const
+{
+	return QPen (m_darkbg ? Qt::white : Qt::black);
+}
+
+QPen GLRenderer::linePen() const
+{
+	QPen linepen (m_thinBorderPen);
+	linepen.setWidth (2);
+	linepen.setColor (luma (m_bgcolor) < 40 ? Qt::white : Qt::black);
+	return linepen;
+}
+
 // =============================================================================
 //
 void GLRenderer::paintEvent (QPaintEvent*)
@@ -593,8 +593,6 @@
 	initGLData();
 	drawGLScene();
 
-	const QPen textpen (m_darkbg ? Qt::white : Qt::black);
-	const QBrush polybrush (QColor (64, 192, 0, 128));
 	QPainter paint (this);
 	QFontMetrics metrics = QFontMetrics (QFont());
 	paint.setRenderHint (QPainter::HighQualityAntialiasing);
@@ -609,13 +607,13 @@
 		QString text = format ("Rotation: (%1, %2, %3)\nPanning: (%4, %5), Zoom: %6",
 			rot(X), rot(Y), rot(Z), pan(X), pan(Y), zoom());
 		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
-		paint.setPen (textpen);
+		paint.setPen (textPen());
 		paint.drawText ((width() - textSize.width()) / 2, height() - textSize.height(), textSize.width(),
 			textSize.height(), Qt::AlignCenter, text);
 	}
 #endif
 
-	if (camera() != EFreeCamera && !isPicking())
+	if (camera() != EFreeCamera && not isPicking())
 	{
 		// Paint the overlay image if we have one
 		const LDGLOverlay& overlay = currentDocumentData().overlays[camera()];
@@ -634,210 +632,26 @@
 		QString text = format (tr ("X: %1, Y: %2, Z: %3"), m_position3D[X], m_position3D[Y], m_position3D[Z]);
 		QFontMetrics metrics = QFontMetrics (font());
 		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
-		paint.setPen (textpen);
+		paint.setPen (textPen());
 		paint.drawText (m_width - textSize.width(), m_height - 16, textSize.width(),
 			textSize.height(), Qt::AlignCenter, text);
-
-		QPen linepen = m_thinBorderPen;
-		linepen.setWidth (2);
-		linepen.setColor (luma (m_bgcolor) < 40 ? Qt::white : Qt::black);
-
-		// Mode-specific rendering
-		if (editMode() == EDrawMode)
-		{
-			QPoint poly[4];
-			Vertex poly3d[4];
-			int numverts = 4;
-
-			// Calculate polygon data
-			if (not m_rectdraw)
-			{
-				numverts = m_drawedVerts.size() + 1;
-				int i = 0;
-
-				for (Vertex& vert : m_drawedVerts)
-					poly3d[i++] = vert;
-
-				// Draw the cursor vertex as the last one in the list.
-				if (numverts <= 4)
-					poly3d[i] = m_position3D;
-				else
-					numverts = 4;
-			}
-			else
-			{
-				// Get vertex information from m_rectverts
-				if (m_drawedVerts.size() > 0)
-					for (int i = 0; i < numverts; ++i)
-						poly3d[i] = m_rectverts[i];
-				else
-					poly3d[0] = m_position3D;
-			}
-
-			// Convert to 2D
-			for (int i = 0; i < numverts; ++i)
-				poly[i] = coordconv3_2 (poly3d[i]);
-
-			if (numverts > 0)
-			{
-				// Draw the polygon-to-be
-				paint.setBrush (polybrush);
-				paint.drawPolygon (poly, numverts);
-
-				// Draw vertex blips
-				for (int i = 0; i < numverts; ++i)
-				{
-					QPoint& blip = poly[i];
-					paint.setPen (linepen);
-					drawBlip (paint, blip);
-
-					// Draw their coordinates
-					paint.setPen (textpen);
-					paint.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true));
-				}
-
-				// Draw line lenghts and angle info if appropriate
-				if (numverts >= 2)
-				{
-					int numlines = (m_drawedVerts.size() == 1) ? 1 : m_drawedVerts.size() + 1;
-					paint.setPen (textpen);
-
-					for (int i = 0; i < numlines; ++i)
-					{
-						const int j = (i + 1 < numverts) ? i + 1 : 0;
-						const int h = (i - 1 >= 0) ? i - 1 : numverts - 1;
-
-						if (cfg::drawLineLengths)
-						{
-							const QString label = QString::number ((poly3d[j] - poly3d[i]).length());
-							QPoint origin = QLineF (poly[i], poly[j]).pointAt (0.5).toPoint();
-							paint.drawText (origin, label);
-						}
-
-						if (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());
-
-							paint.drawText (pos, label);
-						}
-					}
-				}
-			}
-		}
-		elif (editMode() == ECircleMode)
-		{
-			// If we have not specified the center point of the circle yet, preview it on the screen.
-			if (m_drawedVerts.isEmpty())
-				drawBlip (paint, coordconv3_2 (m_position3D));
-			else
-			{
-				QVector<Vertex> verts, verts2;
-				const double dist0 = getCircleDrawDist (0),
-					dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1;
-				const int segs = g_lores;
-				const double angleUnit = (2 * pi) / segs;
-				Axis relX, relY;
-				QVector<QPoint> ringpoints, circlepoints, circle2points;
-
-				getRelativeAxes (relX, relY);
-
-				// Calculate the preview positions of vertices
-				for (int i = 0; i < segs; ++i)
-				{
-					Vertex v = g_origin;
-					v.setCoordinate (relX, m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0));
-					v.setCoordinate (relY, m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0));
-					verts << v;
-
-					if (dist1 != -1)
-					{
-						v.setCoordinate (relX, m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1));
-						v.setCoordinate (relY, m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist1));
-						verts2 << v;
-					}
-				}
-
-				int i = 0;
-				for (const Vertex& v : verts + verts2)
-				{
-					// Calculate the 2D point of the vertex
-					QPoint point = coordconv3_2 (v);
-
-					// Draw a green blip at where it is
-					drawBlip (paint, point);
-
-					// Add it to the list of points for the green ring fill.
-					ringpoints << point;
-
-					// Also add the circle points to separate lists
-					if (i < verts.size())
-						circlepoints << point;
-					else
-						circle2points << point;
-
-					++i;
-				}
-
-				// Insert the first point as the seventeenth one so that
-				// the ring polygon is closed properly.
-				if (ringpoints.size() >= 16)
-					ringpoints.insert (16, ringpoints[0]);
-
-				// Same for the outer ring. Note that the indices are offset by 1
-				// because of the insertion done above bumps the values.
-				if (ringpoints.size() >= 33)
-					ringpoints.insert (33, ringpoints[17]);
-
-				// Draw the ring
-				paint.setBrush ((m_drawedVerts.size() >= 2) ? polybrush : Qt::NoBrush);
-				paint.setPen (Qt::NoPen);
-				paint.drawPolygon (QPolygon (ringpoints));
-
-				// Draw the circles
-				paint.setBrush (Qt::NoBrush);
-				paint.setPen (linepen);
-				paint.drawPolygon (QPolygon (circlepoints));
-				paint.drawPolygon (QPolygon (circle2points));
-
-				{ // Draw the current radius in the middle of the circle.
-					QPoint origin = coordconv3_2 (m_drawedVerts[0]);
-					QString label = QString::number (dist0);
-					paint.setPen (textpen);
-					paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label);
-
-					if (m_drawedVerts.size() >= 2)
-					{
-						label = QString::number (dist1);
-						paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y() + metrics.height(), label);
-					}
-				}
-			}
-		}
 	}
 
-	// Camera icons
 	if (not isPicking())
 	{
+		// Draw edit mode HUD
+		m_editmode->render (paint);
+
 		// Draw a background for the selected camera
 		paint.setPen (m_thinBorderPen);
 		paint.setBrush (QBrush (QColor (0, 128, 160, 128)));
 		paint.drawRect (m_cameraIcons[camera()].selRect);
 
-		// Draw the actual icons
+		// Draw the camera icons
 		for (CameraIcon& info : m_cameraIcons)
 		{
 			// Don't draw the free camera icon when in draw mode
-			if (&info == &m_cameraIcons[EFreeCamera] && not eq (editMode(), ESelectMode, EMagicWandMode))
+			if (&info == &m_cameraIcons[EFreeCamera] && not m_editmode->allowFreeCamera())
 				continue;
 
 			paint.drawPixmap (info.destRect, *info.img, info.srcRect);
@@ -851,7 +665,7 @@
 
 			QString label;
 			label = format (formatstr, tr (g_CameraNames[camera()]));
-			paint.setPen (textpen);
+			paint.setPen (textPen());
 			paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label);
 		}
 
@@ -873,7 +687,7 @@
 	{
 		int y = 0;
 		const int margin = 2;
-		QColor penColor = textpen.color();
+		QColor penColor = textPen().color();
 
 		for (const MessageManager::Line& line : messageLog()->getLines())
 		{
@@ -927,39 +741,21 @@
 
 // =============================================================================
 //
-void GLRenderer::addDrawnVertex (Vertex pos)
-{
-	// If we picked an already-existing vertex, stop drawing
-	if (editMode() == EDrawMode)
-	{
-		for (Vertex& vert : m_drawedVerts)
-		{
-			if (vert == pos)
-			{
-				endDraw (true);
-				return;
-			}
-		}
-	}
-
-	m_drawedVerts << pos;
-}
-
-// =============================================================================
-//
 void GLRenderer::mouseReleaseEvent (QMouseEvent* ev)
 {
 	const bool wasLeft = (m_lastButtons & Qt::LeftButton) && not (ev->buttons() & Qt::LeftButton),
 	   wasRight = (m_lastButtons & Qt::RightButton) && not (ev->buttons() & Qt::RightButton),
 	   wasMid = (m_lastButtons & Qt::MidButton) && not (ev->buttons() & Qt::MidButton);
 
+	Qt::MouseButtons releasedbuttons = m_lastButtons & ~ev->buttons();
+
 	if (m_panning)
 		m_panning = false;
 
 	if (wasLeft)
 	{
 		// Check if we selected a camera icon
-		if (not m_rangepick)
+		if (m_totalmove < 10)
 		{
 			for (CameraIcon & info : m_cameraIcons)
 			{
@@ -971,35 +767,21 @@
 			}
 		}
 
+		if (not isDrawOnly())
+		{
+			AbstractEditMode::MouseEventData data;
+			data.ev = ev;
+			data.mouseMoved = m_totalmove >= 10;
+			data.keymods = m_keymods;
+			data.releasedButtons = releasedbuttons;
+			m_editmode->mouseReleased (data);
+		}
+
 		switch (editMode())
 		{
 			case EDrawMode:
 			{
-				if (m_rectdraw)
-				{
-					if (m_drawedVerts.size() == 2)
-					{
-						endDraw (true);
-						return;
-					}
-				}
-				else
-				{
-					// If we have 4 verts, stop drawing.
-					if (m_drawedVerts.size() >= 4)
-					{
-						endDraw (true);
-						return;
-					}
-
-					if (m_drawedVerts.isEmpty() && ev->modifiers() & Qt::ShiftModifier)
-					{
-						m_rectdraw = true;
-						updateRectVerts();
-					}
-				}
-
-				addDrawnVertex (m_position3D);
+				
 				break;
 			}
 
@@ -1014,35 +796,6 @@
 				addDrawnVertex (m_position3D);
 				break;
 			}
-
-			case ESelectMode:
-			{
-				if (not isDrawOnly())
-				{
-					if (m_totalmove < 10)
-						m_rangepick = false;
-
-					if (not m_rangepick)
-						m_addpick = (m_keymods & Qt::ControlModifier);
-
-					if (m_totalmove < 10 || m_rangepick)
-						pick (ev->x(), ev->y());
-				}
-				break;
-			}
-
-			case EMagicWandMode:
-			{
-				MagicWand::MagicType wandtype = MagicWand::Set;
-
-				if (m_keymods & Qt::ShiftModifier)
-					wandtype = MagicWand::Additive;
-				elif (m_keymods & Qt::ControlModifier)
-					wandtype = MagicWand::Subtractive;
-				
-				m_wand->doMagic (pickOneObject (ev->x(), ev->y()), wandtype);
-				break;
-			}
 		}
 
 		m_rangepick = false;
@@ -1050,55 +803,7 @@
 
 	if (wasMid && editMode() != ESelectMode && m_drawedVerts.size() < 4 && m_totalmove < 10)
 	{
-		// Find the closest vertex to our cursor
-		double			minimumDistance = 1024.0;
-		const Vertex*	closest = null;
-		Vertex			cursorPosition = coordconv2_3 (m_mousePosition, false);
-		QPoint			cursorPosition2D (m_mousePosition);
-		const Axis		relZ = getRelativeZ();
-		QList<Vertex>	vertices;
-
-		for (auto it = document()->vertices().begin(); it != document()->vertices().end(); ++it)
-			vertices << it.key();
-
-		// Sort the vertices in order of distance to camera
-		std::sort (vertices.begin(), vertices.end(), [&](const Vertex& a, const Vertex& b) -> bool
-		{
-			if (g_FixedCameras[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 = 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);
+		
 	}
 
 	if (wasRight && not m_drawedVerts.isEmpty())
@@ -1120,16 +825,6 @@
 void GLRenderer::mousePressEvent (QMouseEvent* ev)
 {
 	m_totalmove = 0;
-
-	if (ev->modifiers() & Qt::ControlModifier)
-	{
-		m_rangepick = true;
-		m_rangeStart.setX (ev->x());
-		m_rangeStart.setY (ev->y());
-		m_addpick = (m_keymods & Qt::AltModifier);
-		ev->accept();
-	}
-
 	m_lastButtons = ev->buttons();
 }
 
@@ -1229,6 +924,10 @@
 //
 void GLRenderer::setCamera (const ECamera cam)
 {
+	// The edit mode may forbid the free camera.
+	if (cam == EFreeCamera && not m_editmode->allowFreeCamera())
+		return;
+
 	m_camera = cam;
 	cfg::camera = (int) cam;
 	g_win->updateEditModeActions();
@@ -1368,55 +1067,17 @@
 
 // =============================================================================
 //
-void GLRenderer::setEditMode (EditMode const& a)
+void GLRenderer::setEditMode (EditModeType a)
 {
-	if (m_editMode == a)
+	if (m_editmode != null && m_editmode->type() == a)
 		return;
 
-	m_editMode = a;
-
-	if (a == EMagicWandMode)
-		m_wand = new MagicWand;
-	else
-	{
-		delete m_wand;
-		m_wand = null;
-	}
-
-	switch (a)
-	{
-		case ESelectMode:
-		case EMagicWandMode:
-		{
-			unsetCursor();
-			setContextMenuPolicy (Qt::DefaultContextMenu);
-		} break;
+	delete m_editmode;
+	m_editmode = AbstractEditMode::createByType (a);
 
-		case EDrawMode:
-		case ECircleMode:
-		{
-			// Cannot draw into the free camera - use top instead.
-			if (camera() == EFreeCamera)
-				setCamera (ETopCamera);
-
-			// Disable the context menu - we need the right mouse button
-			// for removing vertices.
-			setContextMenuPolicy (Qt::NoContextMenu);
-
-			// Use the crosshair cursor when drawing.
-			setCursor (Qt::CrossCursor);
-
-			// Clear the selection when beginning to draw.
-			LDObjectList priorsel = selection();
-			getCurrentDocument()->clearSelection();
-
-			for (LDObjectPtr obj : priorsel)
-				compileObject (obj);
-
-			g_win->updateSelection();
-			m_drawedVerts.clear();
-		} break;
-	}
+	// If we cannot use the free camera, use the top one instead.
+	if (camera() == EFreeCamera && not m_editmode->allowFreeCamera())
+		setCamera (ETopCamera);
 
 	g_win->updateEditModeActions();
 	update();
@@ -1424,6 +1085,13 @@
 
 // =============================================================================
 //
+EditModeType GLRenderer::currentEditModeType() const
+{
+	return m_editmode->type();
+}
+
+// =============================================================================
+//
 void GLRenderer::setDocument (LDDocumentPtr const& a)
 {
 	m_document = a;
@@ -1467,23 +1135,6 @@
 
 // =============================================================================
 //
-Matrix GLRenderer::getCircleDrawMatrix (double scale)
-{
-	Matrix transform = g_circleDrawMatrixTemplates[camera() % 3];
-
-	for (int i = 0; i < 9; ++i)
-	{
-		if (transform[i] == 2)
-			transform[i] = scale;
-		elif (transform[i] == 1 && camera() >= 3)
-			transform[i] = -1;
-	}
-
-	return transform;
-}
-
-// =============================================================================
-//
 void GLRenderer::endDraw (bool accept)
 {
 	(void) accept;
@@ -1676,22 +1327,9 @@
 
 // =============================================================================
 //
-double GLRenderer::getCircleDrawDist (int pos) const
-{
-	assert (m_drawedVerts.size() >= pos + 1);
-	Vertex v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : coordconv2_3 (m_mousePosition, false);
-	Axis relX, relY;
-	getRelativeAxes (relX, relY);
-	double dx = m_drawedVerts[0][relX] - v1[relX];
-	double dy = m_drawedVerts[0][relY] - v1[relY];
-	return Grid::snap (sqrt ((dx * dx) + (dy * dy)), Grid::Coordinate);
-}
-
-// =============================================================================
-//
 void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const
 {
-	const LDFixedCameraInfo* cam = &g_FixedCameras[camera()];
+	const LDFixedCamera* cam = &g_FixedCameras[camera()];
 	relX = cam->axisX;
 	relY = cam->axisY;
 }
@@ -1700,7 +1338,7 @@
 //
 Axis GLRenderer::getRelativeZ() const
 {
-	const LDFixedCameraInfo* cam = &g_FixedCameras[camera()];
+	const LDFixedCamera* cam = &g_FixedCameras[camera()];
 	return (Axis) (3 - cam->axisX - cam->axisY);
 }
 
@@ -1788,7 +1426,7 @@
 	if (camid == (ECamera) -1)
 		camid = camera();
 
-	const LDFixedCameraInfo* cam = &g_FixedCameras[camid];
+	const LDFixedCamera* cam = &g_FixedCameras[camid];
 	return (y) ? cam->axisY : cam->axisX;
 }
 
@@ -2255,3 +1893,13 @@
 		ev->acceptProposedAction();
 	}
 }
+
+Vertex const& GLRenderer::position3D() const
+{
+	return m_position3D;
+}
+
+LDFixedCamera const& GLRenderer::getFixedCamera (ECamera cam)
+{
+	return g_FixedCameras[cam];
+}
--- a/src/glRenderer.h	Fri Jul 04 00:09:37 2014 +0300
+++ b/src/glRenderer.h	Fri Jul 04 22:19:01 2014 +0300
@@ -23,6 +23,7 @@
 #include "ldObject.h"
 #include "ldDocument.h"
 #include "glShared.h"
+#include "editmodes/abstracteditmode.h"
 
 class GLCompiler;
 class MessageManager;
@@ -32,15 +33,7 @@
 class QSpinBox;
 class QLineEdit;
 class QTimer;
-class MagicWand;
-
-enum EditMode
-{
-	ESelectMode,
-	EDrawMode,
-	ECircleMode,
-	EMagicWandMode,
-};
+class MagicWandMode;
 
 //
 // Meta for overlays
@@ -58,7 +51,7 @@
 	bool			invalid;
 };
 
-struct LDFixedCameraInfo
+struct LDFixedCamera
 {
 	const char		glrotate[3];
 	const Axis		axisX,
@@ -150,7 +143,6 @@
 	PROPERTY (public,	MessageManager*,	messageLog, 	setMessageLog,		STOCK_WRITE)
 	PROPERTY (private,	bool,				isPicking,		setPicking,			CUSTOM_WRITE)
 	PROPERTY (public,	LDDocumentPtr,		document,		setDocument,		CUSTOM_WRITE)
-	PROPERTY (public,	EditMode,			editMode,		setEditMode,		CUSTOM_WRITE)
 	PROPERTY (private,	GLCompiler*,		compiler,		setCompiler,		STOCK_WRITE)
 	PROPERTY (public,	LDObjectWeakPtr,	objectAtCursor,	setObjectAtCursor,	STOCK_WRITE)
 	PROPERTY (private,	bool,				isCameraMoving,	setCameraMoving,	STOCK_WRITE)
@@ -162,26 +154,37 @@
 	inline ECamera			camera() const;
 	void					clearOverlay();
 	void					compileObject (LDObjectPtr obj);
+	Vertex					coordconv2_3 (const QPoint& pos2d, bool snap) const;
+	QPoint					coordconv3_2 (const Vertex& pos3d) const;
+	EditModeType			currentEditModeType() const;
+	void					drawBlip (QPainter& paint, QPoint pos) const;
 	void					drawGLScene();
 	void					endDraw (bool accept);
 	void					forgetObject (LDObjectPtr obj);
 	Axis					getCameraAxis (bool y, ECamera camid = (ECamera) -1);
 	const char*				getCameraName() const;
 	double					getDepthValue() const;
+	LDFixedCamera const&	getFixedCamera (ECamera cam) const;
+	void					getRelativeAxes (Axis& relX, Axis& relY) const;
+	Axis					getRelativeZ() const;
 	LDGLOverlay&			getOverlay (int newcam);
 	uchar*					getScreencap (int& w, int& h);
 	void					hardRefresh();
 	void					highlightCursorObject();
 	void					initGLData();
 	void					initOverlaysFromObjects();
+	QPen					linePen() const;
 	void					needZoomToFit();
+	Vertex const&			position3D() const;
 	void					refresh();
 	void					resetAngles();
 	void					resetAllAngles();
 	void					setBackground();
 	void					setCamera (const ECamera cam);
 	void					setDepthValue (double depth);
+	void					setEditMode (EditModeType type);
 	bool					setupOverlay (ECamera cam, QString file, int x, int y, int w, int h);
+	QPen					textPen() const;
 	void					updateOverlayObjects();
 	void					zoomNotch (bool inward);
 
@@ -212,14 +215,11 @@
 	double					m_virtWidth,
 							m_virtHeight;
 	bool					m_darkbg,
-							m_rangepick,
-							m_addpick,
 							m_drawToolTip,
 							m_screencap,
 							m_panning;
 	QPoint					m_mousePosition,
-							m_globalpos,
-							m_rangeStart;
+							m_globalpos;
 	QPen					m_thickBorderPen,
 							m_thinBorderPen;
 	ECamera					m_camera,
@@ -228,25 +228,14 @@
 	int						m_width,
 							m_height,
 							m_totalmove;
-	QList<Vertex>			m_drawedVerts;
-	bool					m_rectdraw;
-	Vertex					m_rectverts[4];
 	QColor					m_bgcolor;
-	MagicWand*				m_wand;
+	AbstractEditMode*		m_editmode;
 
-	void					addDrawnVertex (Vertex m_hoverpos);
 	void					calcCameraIcons();
 	void					clampAngle (double& angle) const;
 	inline LDGLData&		currentDocumentData() const;
-	Vertex					coordconv2_3 (const QPoint& pos2d, bool snap) const;
-	QPoint					coordconv3_2 (const Vertex& pos3d) const;
-	void					drawBlip (QPainter& paint, QPoint pos) const;
 	void					drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type);
 	LDOverlayPtr			findOverlayObject (ECamera cam);
-	double					getCircleDrawDist (int pos) const;
-	Matrix					getCircleDrawMatrix (double scale);
-	void					getRelativeAxes (Axis& relX, Axis& relY) const;
-	Axis					getRelativeZ() const;
 	inline double&			pan (Axis ax);
 	inline const double&	pan (Axis ax) const;
 	void					pick (int mouseX, int mouseY);
--- a/src/ldDocument.cc	Fri Jul 04 00:09:37 2014 +0300
+++ b/src/ldDocument.cc	Fri Jul 04 22:19:01 2014 +0300
@@ -1485,6 +1485,9 @@
 		removeFromSelection (obj);
 
 	assert (m_sel.isEmpty());
+
+	if (self() == getCurrentDocument())
+		g_win->buildObjList();
 }
 
 // =============================================================================
--- a/src/macros.h	Fri Jul 04 00:09:37 2014 +0300
+++ b/src/macros.h	Fri Jul 04 22:19:01 2014 +0300
@@ -63,6 +63,11 @@
 #define readAccess(A) inline decltype(_##A) A() const { return _##A; }
 #define writeAccess(A,B) inline void B (decltype(_##A) const& a) const { _##A = a; }
 
+#define DEFINE_CLASS(SELF, SUPER) \
+public: \
+	using Self = SELF; \
+	using Super = SUPER;
+
 // =============================================================================
 //
 #define elif(A) else if (A)
--- a/src/magicWand.cc	Fri Jul 04 00:09:37 2014 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,187 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "main.h"
-#include "magicWand.h"
-#include "ldDocument.h"
-#include "mainWindow.h"
-
-MagicWand::MagicWand()
-{
-	// Get vertex<->object data
-	for (LDObjectPtr obj : getCurrentDocument()->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)
-			_vertices[obj->vertex (i)] << obj;
-	}
-}
-
-void MagicWand::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 ((*it)->type() != OBJ_Line || (*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. Add to boundaries list and get it off the candidates list.
-				boundaries.append (std::make_tuple ((*it)->vertex (0), (*it)->vertex (1)));
-				break;
-			}
-		}
-	}
-}
-
-void MagicWand::doMagic (LDObjectPtr obj, MagicWand::MagicType type)
-{
-	if (obj == null)
-	{
-		if (type == Set)
-		{
-			getCurrentDocument()->clearSelection();
-			g_win->buildObjList();
-		}
-
-		return;
-	}
-
-	int matchesneeded = 0;
-	QVector<BoundaryType> boundaries;
-	LDObjectType objtype = obj->type();
-
-	if (type != InternalRecursion)
-	{
-		_selection.clear();
-		_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 += _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) ||
-				(candidate->color() != obj->color()) ||
-				(_selection.contains (candidate)) ||
-				(matchesneeded == 1 && (candidate->type() != objtype)) ||
-				((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)) &&
-					eq (matches[1], std::get<0> (boundary), std::get<1> (boundary)))
-				{
-					throw 0;
-				}
-			}
-
-			_selection.append (candidate);
-			doMagic (candidate, InternalRecursion);
-		}
-		catch (int&)
-		{
-			continue;
-		}
-	}
-
-	switch (type)
-	{
-		case Set:
-			getCurrentDocument()->clearSelection();
-		case Additive:
-			for (LDObjectPtr obj : _selection)
-				obj->select();
-			break;
-
-		case Subtractive:
-			for (LDObjectPtr obj : _selection)
-				obj->deselect();
-			break;
-
-		case InternalRecursion:
-			break;
-	}
-
-	if (type != InternalRecursion)
-		g_win->buildObjList();
-}
--- a/src/magicWand.h	Fri Jul 04 00:09:37 2014 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-/*
- *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013, 2014 Santeri Piippo
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-#include "basics.h"
-#include <QMap>
-#include <QVector>
-
-class MagicWand
-{
-	QMap<Vertex, QVector<LDObjectPtr>> _vertices;
-	QVector<LDObjectPtr> _selection;
-
-public:
-	using BoundaryType = std::tuple<Vertex, Vertex>;
-	enum MagicType
-	{
-		Set,
-		Additive,
-		Subtractive,
-		InternalRecursion
-	};
-
-	MagicWand();
-	void doMagic (LDObjectPtr obj, MagicType type);
-
-private:
-	void fillBoundaries (LDObjectPtr obj, QVector<BoundaryType>& boundaries, QVector<LDObjectPtr>& candidates);
-};
--- a/src/mainWindow.cc	Fri Jul 04 00:09:37 2014 +0300
+++ b/src/mainWindow.cc	Fri Jul 04 22:19:01 2014 +0300
@@ -50,6 +50,7 @@
 #include "configuration.h"
 #include "ui_ldforge.h"
 #include "primitives.h"
+#include "editmodes/abstracteditmode.h"
 
 static bool g_isSelectionLocked = false;
 static QMap<QAction*, QKeySequence> g_defaultShortcuts;
@@ -690,11 +691,11 @@
 //
 void MainWindow::updateEditModeActions()
 {
-	const EditMode mode = R()->editMode();
-	ui->actionModeSelect->setChecked (mode == ESelectMode);
-	ui->actionModeDraw->setChecked (mode == EDrawMode);
-	ui->actionModeCircle->setChecked (mode == ECircleMode);
-	ui->actionModeMagicWand->setChecked (mode == EMagicWandMode);
+	const EditModeType mode = R()->currentEditModeType();
+	ui->actionModeSelect->setChecked (mode == EditModeType::Select);
+	ui->actionModeDraw->setChecked (mode == EditModeType::Draw);
+	ui->actionModeCircle->setChecked (mode == EditModeType::Circle);
+	ui->actionModeMagicWand->setChecked (mode == EditModeType::MagicWand);
 }
 
 // =============================================================================

mercurial