Split actions.cpp and actionsEdit.cpp into toolsets.

Sun, 30 Aug 2015 17:20:55 +0300

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Sun, 30 Aug 2015 17:20:55 +0300
changeset 969
b1742ee91d5b
parent 968
4b93b7963456
child 970
c8aae45afd85

Split actions.cpp and actionsEdit.cpp into toolsets.

CMakeLists.txt file | annotate | diff | comparison | revisions
src/actions.cpp file | annotate | diff | comparison | revisions
src/actionsEdit.cpp file | annotate | diff | comparison | revisions
src/extPrograms.cpp file | annotate | diff | comparison | revisions
src/mainwindow.cpp file | annotate | diff | comparison | revisions
src/mainwindow.h file | annotate | diff | comparison | revisions
src/mainwindow.ui file | annotate | diff | comparison | revisions
src/partDownloader.cpp file | annotate | diff | comparison | revisions
src/primitives.cpp file | annotate | diff | comparison | revisions
src/toolsets/algorithmtoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/algorithmtoolset.h file | annotate | diff | comparison | revisions
src/toolsets/basictoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/basictoolset.h file | annotate | diff | comparison | revisions
src/toolsets/extprogramtoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/extprogramtoolset.h file | annotate | diff | comparison | revisions
src/toolsets/filetoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/filetoolset.h file | annotate | diff | comparison | revisions
src/toolsets/movetoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/movetoolset.h file | annotate | diff | comparison | revisions
src/toolsets/toolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/toolset.h file | annotate | diff | comparison | revisions
src/toolsets/viewtoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/viewtoolset.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Sun Aug 30 15:18:41 2015 +0300
+++ b/CMakeLists.txt	Sun Aug 30 17:20:55 2015 +0300
@@ -33,8 +33,6 @@
 include_directories (${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
 
 set (LDFORGE_SOURCES
-	src/actions.cpp
-	src/actionsEdit.cpp
 	src/addObjectDialog.cpp
 	src/basics.cpp
 	src/colors.cpp
@@ -44,7 +42,6 @@
 	src/dialogs.cpp
 	src/documentation.cpp
 	src/editHistory.cpp
-	src/extPrograms.cpp
 	src/glRenderer.cpp
 	src/glCompiler.cpp
 	src/ldDocument.cpp
@@ -71,6 +68,13 @@
 	src/editmodes/magicWandMode.cpp
 	src/editmodes/rectangleMode.cpp
 	src/editmodes/selectMode.cpp
+	src/toolsets/algorithmtoolset.cpp
+	src/toolsets/basictoolset.cpp
+	src/toolsets/extprogramtoolset.cpp
+	src/toolsets/filetoolset.cpp
+	src/toolsets/movetoolset.cpp
+	src/toolsets/toolset.cpp
+	src/toolsets/viewtoolset.cpp
 )
 
 set (LDFORGE_HEADERS
@@ -110,6 +114,13 @@
 	src/editmodes/magicWandMode.h
 	src/editmodes/rectangleMode.h
 	src/editmodes/selectMode.h
+	src/toolsets/algorithmtoolset.h
+	src/toolsets/basictoolset.h
+	src/toolsets/extprogramtoolset.h
+	src/toolsets/filetoolset.h
+	src/toolsets/movetoolset.h
+	src/toolsets/toolset.h
+	src/toolsets/viewtoolset.h
 )
 
 set (LDFORGE_FORMS
--- a/src/actions.cpp	Sun Aug 30 15:18:41 2015 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,883 +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 <QFileDialog>
-#include <QMessageBox>
-#include <QTextEdit>
-#include <QBoxLayout>
-#include <QDialogButtonBox>
-#include <QPushButton>
-#include <QInputDialog>
-
-#include "mainwindow.h"
-#include "ldDocument.h"
-#include "editHistory.h"
-#include "configDialog.h"
-#include "addObjectDialog.h"
-#include "miscallenous.h"
-#include "glRenderer.h"
-#include "dialogs.h"
-#include "primitives.h"
-#include "radioGroup.h"
-#include "colors.h"
-#include "glCompiler.h"
-#include "ldobjectiterator.h"
-#include "dialogs/ldrawpathdialog.h"
-#include "dialogs/newpartdialog.h"
-#include "editmodes/abstractEditMode.h"
-
-EXTERN_CFGENTRY (Bool, DrawWireframe)
-EXTERN_CFGENTRY (Bool, BFCRedGreenView)
-EXTERN_CFGENTRY (Bool, DrawAngles)
-EXTERN_CFGENTRY (Bool, RandomColors)
-EXTERN_CFGENTRY (Bool, DrawSurfaces)
-EXTERN_CFGENTRY (Bool, DrawEdgeLines)
-EXTERN_CFGENTRY (Bool, DrawConditionalLines)
-EXTERN_CFGENTRY (Bool, DrawAxes)
-EXTERN_CFGENTRY (String, LDrawPath)
-EXTERN_CFGENTRY (String, DefaultName)
-EXTERN_CFGENTRY (String, DefaultUser)
-EXTERN_CFGENTRY (Bool, UseCALicense)
-
-// =============================================================================
-//
-void MainWindow::slot_actionNew()
-{
-	NewPartDialog* dlg = new NewPartDialog (this);
-
-	if (dlg->exec() == QDialog::Accepted)
-	{
-		newFile();
-		dlg->fillHeader (CurrentDocument());
-		doFullRefresh();
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionNewFile()
-{
-	newFile();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionOpen()
-{
-	QString name = QFileDialog::getOpenFileName (this, "Open File", "", "LDraw files (*.dat *.ldr)");
-
-	if (name.isEmpty())
-		return;
-
-	OpenMainModel (name);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSave()
-{
-	save (CurrentDocument(), false);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSaveAs()
-{
-	save (CurrentDocument(), true);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSaveAll()
-{
-	for (LDDocument* file : LDDocument::explicitDocuments())
-		save (file, false);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionClose()
-{
-	if (not CurrentDocument()->isSafeToClose())
-		return;
-
-	CurrentDocument()->dismiss();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionCloseAll()
-{
-	if (not IsSafeToCloseAll())
-		return;
-
-	CloseAllDocuments();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSettings()
-{
-	(new ConfigDialog)->exec();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSetLDrawPath()
-{
-	(new LDrawPathDialog (cfg::LDrawPath, true))->exec();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionExit()
-{
-	Exit();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionNewSubfile()
-{
-	AddObjectDialog::staticDialog (OBJ_Subfile, nullptr);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionNewLine()
-{
-	AddObjectDialog::staticDialog (OBJ_Line, nullptr);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionNewTriangle()
-{
-	AddObjectDialog::staticDialog (OBJ_Triangle, nullptr);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionNewQuad()
-{
-	AddObjectDialog::staticDialog (OBJ_Quad, nullptr);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionNewCLine()
-{
-	AddObjectDialog::staticDialog (OBJ_CondLine, nullptr);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionNewComment()
-{
-	AddObjectDialog::staticDialog (OBJ_Comment, nullptr);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionNewBFC()
-{
-	AddObjectDialog::staticDialog (OBJ_BFC, nullptr);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionEdit()
-{
-	if (Selection().size() != 1)
-		return;
-
-	LDObject* obj = Selection().first();
-	AddObjectDialog::staticDialog (obj->type(), obj);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionHelp()
-{
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionAbout()
-{
-	AboutDialog().exec();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionAboutQt()
-{
-	QMessageBox::aboutQt (this);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSelectAll()
-{
-	for (LDObject* obj : CurrentDocument()->objects())
-		obj->select();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSelectByColor()
-{
-	if (Selection().isEmpty())
-		return;
-
-	QList<LDColor> colors;
-
-	for (LDObject* obj : Selection())
-	{
-		if (obj->isColored())
-			colors << obj->color();
-	}
-
-	removeDuplicates (colors);
-	CurrentDocument()->clearSelection();
-
-	for (LDObject* obj : CurrentDocument()->objects())
-	{
-		if (colors.contains (obj->color()))
-			obj->select();
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSelectByType()
-{
-	if (Selection().isEmpty())
-		return;
-
-	QList<LDObjectType> types;
-	QStringList subfilenames;
-
-	for (LDObject* obj : Selection())
-	{
-		types << obj->type();
-
-		if (types.last() == OBJ_Subfile)
-			subfilenames << static_cast<LDSubfile*> (obj)->fileInfo()->name();
-	}
-
-	removeDuplicates (types);
-	removeDuplicates (subfilenames);
-	CurrentDocument()->clearSelection();
-
-	for (LDObject* obj : CurrentDocument()->objects())
-	{
-		LDObjectType type = obj->type();
-
-		if (not types.contains (type))
-			continue;
-
-		// For subfiles, type check is not enough, we check the name of the document as well.
-		if (type == OBJ_Subfile and not subfilenames.contains (static_cast<LDSubfile*> (obj)->fileInfo()->name()))
-			continue;
-
-		obj->select();
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionGridCoarse()
-{
-	cfg::Grid = Grid::Coarse;
-	updateGridToolBar();
-}
-
-void MainWindow::slot_actionGridMedium()
-{
-	cfg::Grid = Grid::Medium;
-	updateGridToolBar();
-}
-
-void MainWindow::slot_actionGridFine()
-{
-	cfg::Grid = Grid::Fine;
-	updateGridToolBar();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionResetView()
-{
-	R()->resetAngles();
-	R()->update();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionInsertFrom()
-{
-	QString fname = QFileDialog::getOpenFileName();
-	int idx = getInsertionPoint();
-
-	if (not fname.length())
-		return;
-
-	QFile f (fname);
-
-	if (not f.open (QIODevice::ReadOnly))
-	{
-		Critical (format ("Couldn't open %1 (%2)", fname, f.errorString()));
-		return;
-	}
-
-	LDObjectList objs = LoadFileContents (&f, null);
-
-	CurrentDocument()->clearSelection();
-
-	for (LDObject* obj : objs)
-	{
-		CurrentDocument()->insertObj (idx, obj);
-		obj->select();
-		R()->compileObject (obj);
-
-		idx++;
-	}
-
-	refresh();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionExportTo()
-{
-	if (Selection().isEmpty())
-		return;
-
-	QString fname = QFileDialog::getSaveFileName();
-
-	if (fname.length() == 0)
-		return;
-
-	QFile file (fname);
-
-	if (not file.open (QIODevice::WriteOnly | QIODevice::Text))
-	{
-		Critical (format ("Unable to open %1 for writing (%2)", fname, file.errorString()));
-		return;
-	}
-
-	for (LDObject* obj : Selection())
-	{
-		QString contents = obj->asText();
-		QByteArray data = contents.toUtf8();
-		file.write (data, data.size());
-		file.write ("\r\n", 2);
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionInsertRaw()
-{
-	int idx = getInsertionPoint();
-
-	QDialog* const dlg = new QDialog;
-	QVBoxLayout* const layout = new QVBoxLayout;
-	QTextEdit* const te_edit = new QTextEdit;
-	QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-
-	layout->addWidget (te_edit);
-	layout->addWidget (bbx_buttons);
-	dlg->setLayout (layout);
-	dlg->setWindowTitle (APPNAME " - Insert Raw");
-	dlg->connect (bbx_buttons, SIGNAL (accepted()), dlg, SLOT (accept()));
-	dlg->connect (bbx_buttons, SIGNAL (rejected()), dlg, SLOT (reject()));
-
-	if (dlg->exec() == QDialog::Rejected)
-		return;
-
-	CurrentDocument()->clearSelection();
-
-	for (QString line : QString (te_edit->toPlainText()).split ("\n"))
-	{
-		LDObject* obj = ParseLine (line);
-
-		CurrentDocument()->insertObj (idx, obj);
-		obj->select();
-		idx++;
-	}
-
-	refresh();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionScreenshot()
-{
-	setlocale (LC_ALL, "C");
-
-	int w, h;
-	uchar* imgdata = R()->getScreencap (w, h);
-	QImage img = GetImageFromScreencap (imgdata, w, h);
-
-	QString root = Basename (CurrentDocument()->name());
-
-	if (root.right (4) == ".dat")
-		root.chop (4);
-
-	QString defaultname = (root.length() > 0) ? format ("%1.png", root) : "";
-	QString fname = QFileDialog::getSaveFileName (this, "Save Screencap", defaultname,
-				"PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)");
-
-	if (not fname.isEmpty() and not img.save (fname))
-		Critical (format ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno)));
-
-	delete[] imgdata;
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionAxes()
-{
-	cfg::DrawAxes = not cfg::DrawAxes;
-	updateActions();
-	R()->update();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionVisibilityToggle()
-{
-	for (LDObject* obj : Selection())
-		obj->setHidden (not obj->isHidden());
-
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionVisibilityHide()
-{
-	for (LDObject* obj : Selection())
-		obj->setHidden (true);
-
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionVisibilityReveal()
-{
-	for (LDObject* obj : Selection())
-	obj->setHidden (false);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionWireframe()
-{
-	cfg::DrawWireframe = not cfg::DrawWireframe;
-	R()->refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSetOverlay()
-{
-	OverlayDialog dlg;
-
-	if (not dlg.exec())
-		return;
-
-	R()->setupOverlay ((ECamera) dlg.camera(), dlg.fpath(), dlg.ofsx(),
-		dlg.ofsy(), dlg.lwidth(), dlg.lheight());
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionClearOverlay()
-{
-	R()->clearOverlay();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionModeSelect()
-{
-	R()->setEditMode (EditModeType::Select);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionModeDraw()
-{
-	R()->setEditMode (EditModeType::Draw);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionModeRectangle()
-{
-	R()->setEditMode (EditModeType::Rectangle);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionModeCircle()
-{
-	R()->setEditMode (EditModeType::Circle);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionModeMagicWand()
-{
- 	R()->setEditMode (EditModeType::MagicWand);
-}
-
-void MainWindow::slot_actionModeLinePath()
-{
-	R()->setEditMode (EditModeType::LinePath);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionDrawAngles()
-{
-	cfg::DrawAngles = not cfg::DrawAngles;
-	R()->refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSetDrawDepth()
-{
-	if (R()->camera() == EFreeCamera)
-		return;
-
-	bool ok;
-	double depth = QInputDialog::getDouble (this, "Set Draw Depth",
-											format ("Depth value for %1 Camera:", R()->getCameraName()),
-											R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok);
-
-	if (ok)
-		R()->setDepthValue (depth);
-}
-
-#if 0
-// This is a test to draw a dummy axle. Meant to be used as a primitive gallery,
-// but I can't figure how to generate these pictures properly. Multi-threading
-// these is an immense pain.
-void MainWindow::slot_actiontestpic()
-{
-	LDDocument* file = getFile ("axle.dat");
-	setlocale (LC_ALL, "C");
-
-	if (not file)
-	{
-		critical ("couldn't load axle.dat");
-		return;
-	}
-
-	int w, h;
-
-	GLRenderer* rend = new GLRenderer;
-	rend->resize (64, 64);
-	rend->setAttribute (Qt::WA_DontShowOnScreen);
-	rend->show();
-	rend->setFile (file);
-	rend->setDrawOnly (true);
-	rend->compileAllObjects();
-	rend->initGLData();
-	rend->drawGLScene();
-
-	uchar* imgdata = rend->screencap (w, h);
-	QImage img = imageFromScreencap (imgdata, w, h);
-
-	if (img.isNull())
-	{
-		critical ("Failed to create the image!\n");
-	}
-	else
-	{
-		QLabel* label = new QLabel;
-		QDialog* dlg = new QDialog;
-		label->setPixmap (QPixmap::fromImage (img));
-		QVBoxLayout* layout = new QVBoxLayout (dlg);
-		layout->addWidget (label);
-		dlg->exec();
-	}
-
-	delete[] imgdata;
-	rend->deleteLater();
-}
-#endif
-
-// =============================================================================
-//
-void MainWindow::slot_actionScanPrimitives()
-{
-	PrimitiveScanner::start();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionBFCView()
-{
-	cfg::BFCRedGreenView = not cfg::BFCRedGreenView;
-
-	if (cfg::BFCRedGreenView)
-		cfg::RandomColors = false;
-
-	updateActions();
-	R()->refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionJumpTo()
-{
-	bool ok;
-	int defval = 0;
-	LDObject* obj;
-
-	if (Selection().size() == 1)
-		defval = Selection()[0]->lineNumber();
-
-	int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval,
-		1, CurrentDocument()->getObjectCount(), 1, &ok);
-
-	if (not ok or (obj = CurrentDocument()->getObject (idx - 1)) == null)
-		return;
-
-	CurrentDocument()->clearSelection();
-	obj->select();
-	updateSelection();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSubfileSelection()
-{
-	if (Selection().size() == 0)
-		return;
-
-	QString			parentpath (CurrentDocument()->fullPath());
-
-	// BFC type of the new subfile - it shall inherit the BFC type of the parent document
-	BFCStatement	bfctype (BFCStatement::NoCertify);
-
-	// Dirname of the new subfile
-	QString			subdirname (Dirname (parentpath));
-
-	// Title of the new subfile
-	QString			subtitle;
-
-	// Comment containing the title of the parent document
-	LDComment*	titleobj = dynamic_cast<LDComment*> (CurrentDocument()->getObject (0));
-
-	// License text for the subfile
-	QString			license (PreferredLicenseText());
-
-	// LDraw code body of the new subfile (i.e. code of the selection)
-	QStringList		code;
-
-	// Full path of the subfile to be
-	QString			fullsubname;
-
-	// Where to insert the subfile reference?
-	int				refidx (Selection()[0]->lineNumber());
-
-	// Determine title of subfile
-	if (titleobj != null)
-		subtitle = "~" + titleobj->text();
-	else
-		subtitle = "~subfile";
-
-	// Remove duplicate tildes
-	while (subtitle.startsWith ("~~"))
-		subtitle.remove (0, 1);
-
-	// If this the parent document isn't already in s/, we need to stuff it into
-	// a subdirectory named s/. Ensure it exists!
-	QString topdirname = Basename (Dirname (CurrentDocument()->fullPath()));
-
-	if (topdirname != "s")
-	{
-		QString desiredPath = subdirname + "/s";
-		QString title = tr ("Create subfile directory?");
-		QString text = format (tr ("The directory <b>%1</b> is suggested for "
-			"subfiles. This directory does not exist, create it?"), desiredPath);
-
-		if (QDir (desiredPath).exists() or Confirm (title, text))
-		{
-			subdirname = desiredPath;
-			QDir().mkpath (subdirname);
-		}
-		else
-			return;
-	}
-
-	// Determine the body of the name of the subfile
-	if (not parentpath.isEmpty())
-	{
-		// Chop existing '.dat' suffix
-		if (parentpath.endsWith (".dat"))
-			parentpath.chop (4);
-
-		// Remove the s?? suffix if it's there, otherwise we'll get filenames
-		// like s01s01.dat when subfiling subfiles.
-		QRegExp subfilesuffix ("s[0-9][0-9]$");
-		if (subfilesuffix.indexIn (parentpath) != -1)
-			parentpath.chop (subfilesuffix.matchedLength());
-
-		int subidx = 1;
-		QString digits;
-
-		// Now find the appropriate filename. Increase the number of the subfile
-		// until we find a name which isn't already taken.
-		do
-		{
-			digits.setNum (subidx++);
-
-			// pad it with a zero
-			if (digits.length() == 1)
-				digits.prepend ("0");
-
-			fullsubname = subdirname + "/" + Basename (parentpath) + "s" + digits + ".dat";
-		} while (FindDocument ("s\\" + Basename (fullsubname)) != null or QFile (fullsubname).exists());
-	}
-
-	// Determine the BFC winding type used in the main document - it is to
-	// be carried over to the subfile.
-	for (LDObjectIterator<LDBFC> it (CurrentDocument()); it.isValid(); ++it)
-	{
-		if (isOneOf (it->statement(), BFCStatement::CertifyCCW, BFCStatement::CertifyCW, BFCStatement::NoCertify))
-		{
-			bfctype = it->statement();
-			break;
-		}
-	}
-
-	// Get the body of the document in LDraw code
-	for (LDObject* obj : Selection())
-		code << obj->asText();
-
-	// Create the new subfile document
-	LDDocument* doc = LDDocument::createNew();
-	doc->setImplicit (false);
-	doc->setFullPath (fullsubname);
-	doc->setName (LDDocument::shortenName (fullsubname));
-
-	LDObjectList objs;
-	objs << LDSpawn<LDComment> (subtitle);
-	objs << LDSpawn<LDComment> ("Name: "); // This gets filled in when the subfile is saved
-	objs << LDSpawn<LDComment> (format ("Author: %1 [%2]", cfg::DefaultName, cfg::DefaultUser));
-	objs << LDSpawn<LDComment> ("!LDRAW_ORG Unofficial_Subpart");
-
-	if (not license.isEmpty())
-		objs << LDSpawn<LDComment> (license);
-
-	objs << LDSpawn<LDEmpty>();
-	objs << LDSpawn<LDBFC> (bfctype);
-	objs << LDSpawn<LDEmpty>();
-
-	doc->addObjects (objs);
-
-	// Add the actual subfile code to the new document
-	for (QString line : code)
-	{
-		LDObject* obj = ParseLine (line);
-		doc->addObject (obj);
-	}
-
-	// Try save it
-	if (save (doc, true))
-	{
-		// Save was successful. Delete the original selection now from the
-		// main document.
-		for (LDObject* obj : Selection())
-			obj->destroy();
-
-		// Add a reference to the new subfile to where the selection was
-		LDSubfile* ref (LDSpawn<LDSubfile>());
-		ref->setColor (MainColor);
-		ref->setFileInfo (doc);
-		ref->setPosition (Origin);
-		ref->setTransform (IdentityMatrix);
-		CurrentDocument()->insertObj (refidx, ref);
-
-		// Refresh stuff
-		updateDocumentList();
-		doFullRefresh();
-	}
-	else
-	{
-		// Failed to save.
-		doc->dismiss();
-	}
-}
-
-void MainWindow::slot_actionRandomColors()
-{
-	cfg::RandomColors = not cfg::RandomColors;
-
-	if (cfg::RandomColors)
-		cfg::BFCRedGreenView = false;
-
-	updateActions();
-	R()->refresh();
-}
-
-void MainWindow::slot_actionOpenSubfiles()
-{
-	for (LDObject* obj : Selection())
-	{
-		LDSubfile* ref = dynamic_cast<LDSubfile*> (obj);
-
-		if (ref == null or not ref->fileInfo()->isImplicit())
-			continue;
-
-		ref->fileInfo()->setImplicit (false);
-	}
-}
-
-void MainWindow::slot_actionDrawSurfaces()
-{
-	cfg::DrawSurfaces = not cfg::DrawSurfaces;
-	updateActions();
-	update();
-}
-
-void MainWindow::slot_actionDrawEdgeLines()
-{
-	cfg::DrawEdgeLines = not cfg::DrawEdgeLines;
-	updateActions();
-	update();
-}
-
-void MainWindow::slot_actionDrawConditionalLines()
-{
-	cfg::DrawConditionalLines = not cfg::DrawConditionalLines;
-	updateActions();
-	update();
-}
--- a/src/actionsEdit.cpp	Sun Aug 30 15:18:41 2015 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,730 +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 <limits>
-#include <QSpinBox>
-#include <QCheckBox>
-#include <QBoxLayout>
-#include <QClipboard>
-#include <QInputDialog>
-#include "mainwindow.h"
-#include "main.h"
-#include "ldDocument.h"
-#include "dialogs/colorselector.h"
-#include "miscallenous.h"
-#include "radioGroup.h"
-#include "glRenderer.h"
-#include "dialogs.h"
-#include "colors.h"
-#include "ldObjectMath.h"
-#include "ui_replcoords.h"
-#include "ui_editraw.h"
-#include "ui_flip.h"
-#include "ui_addhistoryline.h"
-#include "ldobjectiterator.h"
-
-EXTERN_CFGENTRY (String, DefaultUser)
-
-CFGENTRY (Int, RoundPosition,		3)
-CFGENTRY (Int, RoundMatrix,		4)
-CFGENTRY (Int, SplitLinesSegments, 5)
-
-// =============================================================================
-//
-static int CopyToClipboard()
-{
-	LDObjectList objs = Selection();
-	int num = 0;
-
-	// Clear the clipboard first.
-	qApp->clipboard()->clear();
-
-	// Now, copy the contents into the clipboard.
-	QString data;
-
-	for (LDObject* obj : objs)
-	{
-		if (not data.isEmpty())
-			data += "\n";
-
-		data += obj->asText();
-		++num;
-	}
-
-	qApp->clipboard()->setText (data);
-	return num;
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionCut()
-{
-	int num = CopyToClipboard();
-	deleteSelection();
-	print (tr ("%1 objects cut"), num);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionCopy()
-{
-	int num = CopyToClipboard();
-	print (tr ("%1 objects copied"), num);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionPaste()
-{
-	const QString clipboardText = qApp->clipboard()->text();
-	int idx = getInsertionPoint();
-	CurrentDocument()->clearSelection();
-	int num = 0;
-
-	for (QString line : clipboardText.split ("\n"))
-	{
-		LDObject* pasted = ParseLine (line);
-		CurrentDocument()->insertObj (idx++, pasted);
-		pasted->select();
-		++num;
-	}
-
-	print (tr ("%1 objects pasted"), num);
-	refresh();
-	scrollToSelection();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionDelete()
-{
-	int num = deleteSelection();
-	print (tr ("%1 objects deleted"), num);
-}
-
-// =============================================================================
-//
-static void DoInline (bool deep)
-{
-	for (LDObjectIterator<LDSubfile> it (Selection()); it.isValid(); ++it)
-	{
-		// Get the index of the subfile so we know where to insert the
-		// inlined contents.
-		int idx = it->lineNumber();
-
-		if (idx != -1)
-		{
-			LDObjectList objs = it->inlineContents (deep, false);
-	
-			// Merge in the inlined objects
-			for (LDObject* inlineobj : objs)
-			{
-				QString line = inlineobj->asText();
-				inlineobj->destroy();
-				LDObject* newobj = ParseLine (line);
-				CurrentDocument()->insertObj (idx++, newobj);
-				newobj->select();
-			}
-	
-			// Delete the subfile now as it's been inlined.
-			it->destroy();
-		}
-	}
-}
-
-void MainWindow::slot_actionInline()
-{
-	DoInline (false);
-	refresh();
-}
-
-void MainWindow::slot_actionInlineDeep()
-{
-	DoInline (true);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSplitQuads()
-{
-	int num = 0;
-
-	for (LDObjectIterator<LDQuad> it (Selection()); it.isValid(); ++it)
-	{
-		// Find the index of this quad
-		long index = it->lineNumber();
-
-		if (index == -1)
-			return;
-
-		QList<LDTriangle*> triangles = it->splitToTriangles();
-
-		// Replace the quad with the first triangle and add the second triangle
-		// after the first one.
-		CurrentDocument()->setObject (index, triangles[0]);
-		CurrentDocument()->insertObj (index + 1, triangles[1]);
-		num++;
-	}
-
-	print ("%1 quadrilaterals split", num);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionEditRaw()
-{
-	if (Selection().size() != 1)
-		return;
-
-	LDObject* obj = Selection()[0];
-	QDialog* dlg = new QDialog;
-	Ui::EditRawUI ui;
-
-	ui.setupUi (dlg);
-	ui.code->setText (obj->asText());
-
-	if (obj->type() == OBJ_Error)
-		ui.errorDescription->setText (static_cast<LDError*> (obj)->reason());
-	else
-	{
-		ui.errorDescription->hide();
-		ui.errorIcon->hide();
-	}
-
-	if (dlg->exec() == QDialog::Rejected)
-		return;
-
-	// Reinterpret it from the text of the input field
-	LDObject* newobj = ParseLine (ui.code->text());
-	obj->replace (newobj);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionSetColor()
-{
-	if (Selection().isEmpty())
-		return;
-
-	LDObjectList objs = Selection();
-
-	// If all selected objects have the same color, said color is our default
-	// value to the color selection dialog.
-	LDColor color;
-	LDColor defaultcol = getSelectedColor();
-
-	// Show the dialog to the user now and ask for a color.
-	if (ColorSelector::selectColor (color, defaultcol, this))
-	{
-		for (LDObject* obj : objs)
-		{
-			if (obj->isColored())
-				obj->setColor (color);
-		}
-
-		refresh();
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionBorders()
-{
-	LDObjectList objs = Selection();
-	int num = 0;
-
-	for (LDObject* obj : objs)
-	{
-		const LDObjectType type = obj->type();
-
-		if (type != OBJ_Quad and type != OBJ_Triangle)
-			continue;
-
-		LDLine* lines[4];
-
-		if (type == OBJ_Quad)
-		{
-			LDQuad* quad = static_cast<LDQuad*> (obj);
-			lines[0] = LDSpawn<LDLine> (quad->vertex (0), quad->vertex (1));
-			lines[1] = LDSpawn<LDLine> (quad->vertex (1), quad->vertex (2));
-			lines[2] = LDSpawn<LDLine> (quad->vertex (2), quad->vertex (3));
-			lines[3] = LDSpawn<LDLine> (quad->vertex (3), quad->vertex (0));
-		}
-		else
-		{
-			LDTriangle* tri = static_cast<LDTriangle*> (obj);
-			lines[0] = LDSpawn<LDLine> (tri->vertex (0), tri->vertex (1));
-			lines[1] = LDSpawn<LDLine> (tri->vertex (1), tri->vertex (2));
-			lines[2] = LDSpawn<LDLine> (tri->vertex (2), tri->vertex (0));
-			lines[3] = nullptr;
-		}
-
-		for (int i = 0; i < countof (lines); ++i)
-		{
-			if (lines[i] == null)
-				continue;
-
-			long idx = obj->lineNumber() + i + 1;
-			CurrentDocument()->insertObj (idx, lines[i]);
-			++num;
-		}
-	}
-
-	print (tr ("Added %1 border lines"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-static void MoveSelection (MainWindow* win, bool up)
-{
-	LDObjectList objs = Selection();
-	LDObject::moveObjects (objs, up);
-	win->buildObjList();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionMoveUp()
-{
-	MoveSelection (this, true);
-}
-
-void MainWindow::slot_actionMoveDown()
-{
-	MoveSelection (this, false);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionUndo()
-{
-	CurrentDocument()->undo();
-}
-
-void MainWindow::slot_actionRedo()
-{
-	CurrentDocument()->redo();
-}
-
-// =============================================================================
-//
-static void MoveObjects (Vertex vect)
-{
-	// Apply the grid values
-	vect *= *CurrentGrid().coordinateSnap;
-
-	for (LDObject* obj : Selection())
-		obj->move (vect);
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionMoveXNeg()
-{
-	MoveObjects ({-1, 0, 0});
-}
-
-void MainWindow::slot_actionMoveYNeg()
-{
-	MoveObjects ({0, -1, 0});
-}
-
-void MainWindow::slot_actionMoveZNeg()
-{
-	MoveObjects ({0, 0, -1});
-}
-
-void MainWindow::slot_actionMoveXPos()
-{
-	MoveObjects ({1, 0, 0});
-}
-
-void MainWindow::slot_actionMoveYPos()
-{
-	MoveObjects ({0, 1, 0});
-}
-
-void MainWindow::slot_actionMoveZPos()
-{
-	MoveObjects ({0, 0, 1});
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionInvert()
-{
-	for (LDObject* obj : Selection())
-		obj->invert();
-
-	refresh();
-}
-
-// =============================================================================
-//
-static double GetRotateActionAngle()
-{
-	return (Pi * *CurrentGrid().angleSnap) / 180;
-}
-
-void MainWindow::slot_actionRotateXPos()
-{
-	RotateObjects (1, 0, 0, GetRotateActionAngle(), Selection());
-}
-void MainWindow::slot_actionRotateYPos()
-{
-	RotateObjects (0, 1, 0, GetRotateActionAngle(), Selection());
-}
-void MainWindow::slot_actionRotateZPos()
-{
-	RotateObjects (0, 0, 1, GetRotateActionAngle(), Selection());
-}
-void MainWindow::slot_actionRotateXNeg()
-{
-	RotateObjects (-1, 0, 0, GetRotateActionAngle(), Selection());
-}
-void MainWindow::slot_actionRotateYNeg()
-{
-	RotateObjects (0, -1, 0, GetRotateActionAngle(), Selection());
-}
-void MainWindow::slot_actionRotateZNeg()
-{
-	RotateObjects (0, 0, -1, GetRotateActionAngle(), Selection());
-}
-
-void MainWindow::slot_actionRotationPoint()
-{
-	ConfigureRotationPoint();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionRoundCoordinates()
-{
-	setlocale (LC_ALL, "C");
-	int num = 0;
-
-	for (LDObject* obj : Selection())
-	{
-		LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
-
-		if (mo != null)
-		{
-			Vertex v = mo->position();
-			Matrix t = mo->transform();
-
-			// Note: matrix values are to be rounded to 4 decimals.
-			v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
-			ApplyToMatrix (t, [](int, double& a) { RoundToDecimals (a, cfg::RoundMatrix); });
-
-			mo->setPosition (v);
-			mo->setTransform (t);
-			num += 12;
-		}
-		else
-		{
-			for (int i = 0; i < obj->numVertices(); ++i)
-			{
-				Vertex v = obj->vertex (i);
-				v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
-				obj->setVertex (i, v);
-				num += 3;
-			}
-		}
-	}
-
-	print (tr ("Rounded %1 values"), num);
-	refreshObjectList();
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionUncolor()
-{
-	int num = 0;
-
-	for (LDObject* obj : Selection())
-	{
-		if (not obj->isColored())
-			continue;
-
-		obj->setColor (obj->defaultColor());
-		num++;
-	}
-
-	print (tr ("%1 objects uncolored"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionReplaceCoords()
-{
-	QDialog* dlg = new QDialog (this);
-	Ui::ReplaceCoordsUI ui;
-	ui.setupUi (dlg);
-
-	if (not dlg->exec())
-		return;
-
-	const double search = ui.search->value(),
-		replacement = ui.replacement->value();
-	const bool any = ui.any->isChecked(),
-		rel = ui.relative->isChecked();
-
-	QList<Axis> sel;
-	int num = 0;
-
-	if (ui.x->isChecked()) sel << X;
-	if (ui.y->isChecked()) sel << Y;
-	if (ui.z->isChecked()) sel << Z;
-
-	for (LDObject* obj : Selection())
-	{
-		for (int i = 0; i < obj->numVertices(); ++i)
-		{
-			Vertex v = obj->vertex (i);
-
-			v.apply ([&](Axis ax, double& coord)
-			{
-				if (not sel.contains (ax) or
-					(not any and coord != search))
-				{
-					return;
-				}
-
-				if (not rel)
-					coord = 0;
-
-				coord += replacement;
-				num++;
-			});
-
-			obj->setVertex (i, v);
-		}
-	}
-
-	print (tr ("Altered %1 values"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionFlip()
-{
-	QDialog* dlg = new QDialog;
-	Ui::FlipUI ui;
-	ui.setupUi (dlg);
-
-	if (not dlg->exec())
-		return;
-
-	QList<Axis> sel;
-
-	if (ui.x->isChecked()) sel << X;
-	if (ui.y->isChecked()) sel << Y;
-	if (ui.z->isChecked()) sel << Z;
-
-	for (LDObject* obj : Selection())
-	{
-		for (int i = 0; i < obj->numVertices(); ++i)
-		{
-			Vertex v = obj->vertex (i);
-
-			v.apply ([&](Axis ax, double& a)
-			{
-				if (sel.contains (ax))
-					a = -a;
-			});
-
-			obj->setVertex (i, v);
-		}
-	}
-
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionDemote()
-{
-	int num = 0;
-
-	for (LDObjectIterator<LDCondLine> it (Selection()); it.isValid(); ++it)
-	{
-		it->toEdgeLine();
-		++num;
-	}
-
-	print (tr ("Converted %1 conditional lines"), num);
-	refresh();
-}
-
-// =============================================================================
-//
-static bool IsColorUsed (LDColor color)
-{
-	for (LDObject* obj : CurrentDocument()->objects())
-	{
-		if (obj->isColored() and obj->color() == color)
-			return true;
-	}
-
-	return false;
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionAutocolor()
-{
-	LDColor color;
-
-	for (color = 0; color.isLDConfigColor(); ++color)
-	{
-		if (color.isValid() and not IsColorUsed (color))
-			break;
-	}
-
-	if (not color.isLDConfigColor())
-	{
-		print (tr ("Cannot auto-color: all colors are in use!"));
-		return;
-	}
-
-	for (LDObject* obj : Selection())
-	{
-		if (not obj->isColored())
-			continue;
-
-		obj->setColor (color);
-	}
-
-	print (tr ("Auto-colored: new color is [%1] %2"), color.index(), color.name());
-	refresh();
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionAddHistoryLine()
-{
-	LDObject* obj;
-	bool ishistory = false;
-	bool prevIsHistory = false;
-
-	QDialog* dlg = new QDialog;
-	Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
-	ui->setupUi (dlg);
-	ui->m_username->setText (cfg::DefaultUser);
-	ui->m_date->setDate (QDate::currentDate());
-	ui->m_comment->setFocus();
-
-	if (not dlg->exec())
-		return;
-
-	// Create the comment object based on input
-	LDComment* comment = new LDComment (format ("!HISTORY %1 [%2] %3",
-		ui->m_date->date().toString ("yyyy-MM-dd"),
-		ui->m_username->text(),
-		ui->m_comment->text()));
-
-	// Find a spot to place the new comment
-	for (obj = CurrentDocument()->getObject (0);
-		obj and obj->next() and not obj->next()->isScemantic();
-		obj = obj->next())
-	{
-		LDComment* comment = dynamic_cast<LDComment*> (obj);
-
-		if (comment and comment->text().startsWith ("!HISTORY "))
-			ishistory = true;
-
-		if (prevIsHistory and not ishistory)
-			break; // Last line was history, this isn't, thus insert the new history line here.
-
-		prevIsHistory = ishistory;
-	}
-
-	int idx = obj ? obj->lineNumber() : 0;
-	CurrentDocument()->insertObj (idx++, comment);
-
-	// If we're adding a history line right before a scemantic object, pad it
-	// an empty line
-	if (obj and obj->next() and obj->next()->isScemantic())
-		CurrentDocument()->insertObj (idx, new LDEmpty);
-
-	buildObjList();
-	delete ui;
-}
-
-void MainWindow::slot_actionSplitLines()
-{
-	bool ok;
-	int segments = QInputDialog::getInt (this, APPNAME, "Amount of segments:", cfg::SplitLinesSegments, 0,
-		std::numeric_limits<int>::max(), 1, &ok);
-
-	if (not ok)
-		return;
-
-	cfg::SplitLinesSegments = segments;
-
-	for (LDObject* obj : Selection())
-	{
-		if (not isOneOf (obj->type(), OBJ_Line, OBJ_CondLine))
-			continue;
-
-		QVector<LDObject*> newsegs;
-
-		for (int i = 0; i < segments; ++i)
-		{
-			LDObject* segment;
-			Vertex v0, v1;
-
-			v0.apply ([&](Axis ax, double& a)
-			{
-				double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
-				a = (obj->vertex (0)[ax] + ((len * i) / segments));
-			});
-
-			v1.apply ([&](Axis ax, double& a)
-			{
-				double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
-				a = (obj->vertex (0)[ax] + ((len * (i + 1)) / segments));
-			});
-
-			if (obj->type() == OBJ_Line)
-				segment = LDSpawn<LDLine> (v0, v1);
-			else
-				segment = LDSpawn<LDCondLine> (v0, v1, obj->vertex (2), obj->vertex (3));
-
-			newsegs << segment;
-		}
-
-		int ln = obj->lineNumber();
-
-		for (LDObject* seg : newsegs)
-			CurrentDocument()->insertObj (ln++, seg);
-
-		obj->destroy();
-	}
-
-	buildObjList();
-	refresh();
-}
--- a/src/extPrograms.cpp	Sun Aug 30 15:18:41 2015 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,721 +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 <QProcess>
-#include <QTemporaryFile>
-#include <QDialog>
-#include <QDialogButtonBox>
-#include <QSpinBox>
-#include <QCheckBox>
-#include <QComboBox>
-#include <QGridLayout>
-#include <QFileInfo>
-#include "main.h"
-#include "configuration.h"
-#include "miscallenous.h"
-#include "mainwindow.h"
-#include "ldDocument.h"
-#include "radioGroup.h"
-#include "editHistory.h"
-#include "ui_ytruder.h"
-#include "ui_intersector.h"
-#include "ui_rectifier.h"
-#include "ui_coverer.h"
-#include "ui_isecalc.h"
-#include "ui_edger2.h"
-#include "dialogs.h"
-
-enum extprog
-{
-	Isecalc,
-	Intersector,
-	Coverer,
-	Ytruder,
-	Rectifier,
-	Edger2,
-};
-
-// =============================================================================
-//
-CFGENTRY (String, IsecalcPath, "")
-CFGENTRY (String, IntersectorPath, "")
-CFGENTRY (String, CovererPath, "")
-CFGENTRY (String, YtruderPath, "")
-CFGENTRY (String, RectifierPath, "")
-CFGENTRY (String, Edger2Path, "")
-
-QString* const g_extProgPaths[] =
-{
-	&cfg::IsecalcPath,
-	&cfg::IntersectorPath,
-	&cfg::CovererPath,
-	&cfg::YtruderPath,
-	&cfg::RectifierPath,
-	&cfg::Edger2Path,
-};
-
-CFGENTRY (Bool, IsecalcUsesWine, false)
-CFGENTRY (Bool, IntersectorUsesWine, false)
-CFGENTRY (Bool, CovererUsesWine, false)
-CFGENTRY (Bool, YtruderUsesWine, false)
-CFGENTRY (Bool, RectifierUsesWine, false)
-CFGENTRY (Bool, Edger2UsesWine, false)
-
-bool* const g_extProgWine[] =
-{
-	&cfg::IsecalcUsesWine,
-	&cfg::IntersectorUsesWine,
-	&cfg::CovererUsesWine,
-	&cfg::YtruderUsesWine,
-	&cfg::RectifierUsesWine,
-	&cfg::Edger2UsesWine,
-};
-
-const char* g_extProgNames[] =
-{
-	"Isecalc",
-	"Intersector",
-	"Coverer",
-	"Ytruder",
-	"Rectifier",
-	"Edger2"
-};
-
-// =============================================================================
-//
-static bool MakeTempFile (QTemporaryFile& tmp, QString& fname)
-{
-	if (not tmp.open())
-		return false;
-
-	fname = tmp.fileName();
-	tmp.close();
-	return true;
-}
-
-// =============================================================================
-//
-static bool CheckExtProgramPath (const extprog prog)
-{
-	QString& path = *g_extProgPaths[prog];
-
-	if (not path.isEmpty())
-		return true;
-
-	ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]);
-
-	if (dlg->exec() and not dlg->getPath().isEmpty())
-	{
-		path = dlg->getPath();
-		return true;
-	}
-
-	return false;
-}
-
-// =============================================================================
-//
-static QString ProcessExtProgError (extprog prog, QProcess& proc)
-{
-	switch (proc.error())
-	{
-		case QProcess::FailedToStart:
-		{
-			QString wineblurb;
-
-#ifndef _WIN32
-			if (*g_extProgWine[prog])
-				wineblurb = "make sure Wine is installed and ";
-#else
-			(void) prog;
-#endif
-
-			return format ("Program failed to start, %1check your permissions", wineblurb);
-		} break;
-
-		case QProcess::Crashed:
-			return "Crashed.";
-
-		case QProcess::WriteError:
-		case QProcess::ReadError:
-			return "I/O error.";
-
-		case QProcess::UnknownError:
-			return "Unknown error";
-
-		case QProcess::Timedout:
-			return format ("Timed out (30 seconds)");
-	}
-
-	return "";
-}
-
-// =============================================================================
-//
-static void WriteObjects (const LDObjectList& objects, QFile& f)
-{
-	for (LDObject* obj : objects)
-	{
-		if (obj->type() == OBJ_Subfile)
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDObjectList objs = ref->inlineContents (true, false);
-
-			WriteObjects (objs, f);
-
-			for (LDObject* obj : objs)
-				obj->destroy();
-		}
-		else
-			f.write ((obj->asText() + "\r\n").toUtf8());
-	}
-}
-
-// =============================================================================
-//
-static void WriteObjects (const LDObjectList& objects, QString fname)
-{
-	// Write the input file
-	QFile f (fname);
-
-	if (not f.open (QIODevice::WriteOnly | QIODevice::Text))
-	{
-		Critical (format ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString()));
-		return;
-	}
-
-	WriteObjects (objects, f);
-	f.close();
-
-#ifdef DEBUG
-	QFile::copy (fname, "debug_lastInput");
-#endif
-}
-
-// =============================================================================
-//
-void WriteSelection (QString fname)
-{
-	WriteObjects (Selection(), fname);
-}
-
-// =============================================================================
-//
-void WriteColorGroup (LDColor color, QString fname)
-{
-	LDObjectList objects;
-
-	for (LDObject* obj : CurrentDocument()->objects())
-	{
-		if (not obj->isColored() or obj->color() != color)
-			continue;
-
-		objects << obj;
-	}
-
-	WriteObjects (objects, fname);
-}
-
-// =============================================================================
-//
-bool RunExtProgram (extprog prog, QString path, QString argvstr)
-{
-	QTemporaryFile input;
-	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
-
-#ifndef _WIN32
-	if (*g_extProgWine[prog])
-	{
-		argv.insert (0, path);
-		path = "wine";
-	}
-#endif // _WIN32
-
-	print ("Running command: %1 %2\n", path, argv.join (" "));
-
-	if (not input.open())
-		return false;
-
-	QProcess proc;
-
-	// Begin!
-	proc.setStandardInputFile (input.fileName());
-	proc.start (path, argv);
-
-	if (not proc.waitForStarted())
-	{
-		Critical (format ("Couldn't start %1: %2\n", g_extProgNames[prog], ProcessExtProgError (prog, proc)));
-		return false;
-	}
-
-	// Write an enter, the utility tools all expect one
-	input.write ("\n");
-
-	// Wait while it runs
-	proc.waitForFinished();
-
-	QString err = "";
-
-	if (proc.exitStatus() != QProcess::NormalExit)
-		err = ProcessExtProgError (prog, proc);
-
-	// Check the return code
-	if (proc.exitCode() != 0)
-		err = format ("Program exited abnormally (return code %1).",  proc.exitCode());
-
-	if (not err.isEmpty())
-	{
-		Critical (format ("%1 failed: %2\n", g_extProgNames[prog], err));
-		QString filename ("externalProgramOutput.txt");
-		QFile file (filename);
-
-		if (file.open (QIODevice::WriteOnly | QIODevice::Text))
-		{
-			file.write (proc.readAllStandardOutput());
-			file.write (proc.readAllStandardError());
-			print ("Wrote output and error logs to %1", QFileInfo (file).absoluteFilePath());
-		}
-		else
-		{
-			print ("Couldn't open %1 for writing: %2",
-				QFileInfo (filename).absoluteFilePath(), file.errorString());
-		}
-
-		return false;
-	}
-
-	return true;
-}
-
-// =============================================================================
-//
-static void InsertOutput (QString fname, bool replace, QList<LDColor> colorsToReplace)
-{
-#ifdef DEBUG
-	QFile::copy (fname, "./debug_lastOutput");
-#endif // RELEASE
-
-	// Read the output file
-	QFile f (fname);
-
-	if (not f.open (QIODevice::ReadOnly))
-	{
-		Critical (format ("Couldn't open temporary file %1 for reading.\n", fname));
-		return;
-	}
-
-	LDObjectList objs = LoadFileContents (&f, null);
-
-	// If we replace the objects, delete the selection now.
-	if (replace)
-		g_win->deleteSelection();
-
-	for (LDColor color : colorsToReplace)
-		g_win->deleteByColor (color);
-
-	// Insert the new objects
-	CurrentDocument()->clearSelection();
-
-	for (LDObject* obj : objs)
-	{
-		if (not obj->isScemantic())
-		{
-			obj->destroy();
-			continue;
-		}
-
-		CurrentDocument()->addObject (obj);
-		obj->select();
-	}
-
-	g_win->doFullRefresh();
-}
-
-// =============================================================================
-// Interface for Ytruder
-// =============================================================================
-void MainWindow::slot_actionYtruder()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Ytruder))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::YtruderUI ui;
-	ui.setupUi (dlg);
-
-	if (not dlg->exec())
-		return;
-
-	// Read the user's choices
-	const enum { Distance, Symmetry, Projection, Radial } mode =
-		ui.mode_distance->isChecked()   ? Distance :
-		ui.mode_symmetry->isChecked()   ? Symmetry :
-		ui.mode_projection->isChecked() ? Projection : Radial;
-
-	const Axis axis =
-		ui.axis_x->isChecked() ? X :
-		ui.axis_y->isChecked() ? Y : Z;
-
-	const double depth = ui.planeDepth->value(),
-				 condAngle = ui.condAngle->value();
-
-	QTemporaryFile indat, outdat;
-	QString inDATName, outDATName;
-
-	// Make temp files for the input and output files
-	if (not MakeTempFile (indat, inDATName) or not MakeTempFile (outdat, outDATName))
-		return;
-
-	// Compose the command-line arguments
-	QString argv = Join (
-	{
-		(axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
-		(mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
-		depth,
-		"-a",
-		condAngle,
-		inDATName,
-		outDATName
-	});
-
-	WriteSelection (inDATName);
-
-	if (not RunExtProgram (Ytruder, cfg::YtruderPath, argv))
-		return;
-
-	InsertOutput (outDATName, false, {});
-}
-
-// =============================================================================
-// Rectifier interface
-// =============================================================================
-void MainWindow::slot_actionRectifier()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Rectifier))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::RectifierUI ui;
-	ui.setupUi (dlg);
-
-	if (not dlg->exec())
-		return;
-
-	QTemporaryFile indat, outdat;
-	QString inDATName, outDATName;
-
-	// Make temp files for the input and output files
-	if (not MakeTempFile (indat, inDATName) or not MakeTempFile (outdat, outDATName))
-		return;
-
-	// Compose arguments
-	QString argv = Join (
-	{
-		(not ui.cb_condense->isChecked()) ? "-q" : "",
-		(not ui.cb_subst->isChecked()) ? "-r" : "",
-		(ui.cb_condlineCheck->isChecked()) ? "-a" : "",
-		(ui.cb_colorize->isChecked()) ? "-c" : "",
-		"-t",
-		ui.dsb_coplthres->value(),
-		inDATName,
-		outDATName
-	});
-
-	WriteSelection (inDATName);
-
-	if (not RunExtProgram (Rectifier, cfg::RectifierPath, argv))
-		return;
-
-	InsertOutput (outDATName, true, {});
-}
-
-// =============================================================================
-// Intersector interface
-// =============================================================================
-void MainWindow::slot_actionIntersector()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Intersector))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::IntersectorUI ui;
-	ui.setupUi (dlg);
-
-	MakeColorComboBox (ui.cmb_incol);
-	MakeColorComboBox (ui.cmb_cutcol);
-	ui.cb_repeat->setWhatsThis ("If this is set, " APPNAME " runs Intersector a second time with inverse files to cut the "
-								" cutter group with the input group. Both groups are cut by the intersection.");
-	ui.cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection.");
-
-	LDColor inCol, cutCol;
-	const bool repeatInverse = ui.cb_repeat->isChecked();
-
-	forever
-	{
-		if (not dlg->exec())
-			return;
-
-		inCol = ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt();
-		cutCol = ui.cmb_cutcol->itemData (ui.cmb_cutcol->currentIndex()).toInt();
-
-		if (inCol == cutCol)
-		{
-			Critical ("Cannot use the same color group for both input and cutter!");
-			continue;
-		}
-
-		break;
-	}
-
-	// Five temporary files!
-	// indat = input group file
-	// cutdat = cutter group file
-	// outdat = primary output
-	// outdat2 = inverse output
-	// edgesdat = edges output (isecalc)
-	QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat;
-	QString inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
-
-	if (not MakeTempFile (indat, inDATName) or
-		not MakeTempFile (cutdat, cutDATName) or
-		not MakeTempFile (outdat, outDATName) or
-		not MakeTempFile (outdat2, outDAT2Name) or
-		not MakeTempFile (edgesdat, edgesDATName))
-	{
-		return;
-	}
-
-	QString parms = Join (
-	{
-		(ui.cb_colorize->isChecked()) ? "-c" : "",
-		(ui.cb_nocondense->isChecked()) ? "-t" : "",
-		"-s",
-		ui.dsb_prescale->value()
-	});
-
-	QString argv_normal = Join (
-	{
-		parms,
-		inDATName,
-		cutDATName,
-		outDATName
-	});
-
-	QString argv_inverse = Join (
-	{
-		parms,
-		cutDATName,
-		inDATName,
-		outDAT2Name
-	});
-
-	WriteColorGroup (inCol, inDATName);
-	WriteColorGroup (cutCol, cutDATName);
-
-	if (not RunExtProgram (Intersector, cfg::IntersectorPath, argv_normal))
-		return;
-
-	InsertOutput (outDATName, false, {inCol});
-
-	if (repeatInverse and RunExtProgram (Intersector, cfg::IntersectorPath, argv_inverse))
-		InsertOutput (outDAT2Name, false, {cutCol});
-
-	if (ui.cb_edges->isChecked() and CheckExtProgramPath (Isecalc) and
-		RunExtProgram (Isecalc, cfg::IsecalcPath, Join ({inDATName, cutDATName, edgesDATName})))
-	{
-		InsertOutput (edgesDATName, false, {});
-	}
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionCoverer()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Coverer))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::CovererUI ui;
-	ui.setupUi (dlg);
-	MakeColorComboBox (ui.cmb_col1);
-	MakeColorComboBox (ui.cmb_col2);
-
-	LDColor in1Col, in2Col;
-
-	forever
-	{
-		if (not dlg->exec())
-			return;
-
-		in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt();
-		in2Col = ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt();
-
-		if (in1Col == in2Col)
-		{
-			Critical ("Cannot use the same color group for both inputs!");
-			continue;
-		}
-
-		break;
-	}
-
-	QTemporaryFile in1dat, in2dat, outdat;
-	QString in1DATName, in2DATName, outDATName;
-
-	if (not MakeTempFile (in1dat, in1DATName) or
-		not MakeTempFile (in2dat, in2DATName) or
-		not MakeTempFile (outdat, outDATName))
-	{
-		return;
-	}
-
-	QString argv = Join (
-	{
-		(ui.cb_oldsweep->isChecked() ? "-s" : ""),
-		(ui.cb_reverse->isChecked() ? "-r" : ""),
-		(ui.dsb_segsplit->value() != 0 ? format ("-l %1", ui.dsb_segsplit->value()) : ""),
-		(ui.sb_bias->value() != 0 ? format ("-s %1", ui.sb_bias->value()) : ""),
-		in1DATName,
-		in2DATName,
-		outDATName
-	});
-
-	WriteColorGroup (in1Col, in1DATName);
-	WriteColorGroup (in2Col, in2DATName);
-
-	if (not RunExtProgram (Coverer, cfg::CovererPath, argv))
-		return;
-
-	InsertOutput (outDATName, false, {});
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionIsecalc()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Isecalc))
-		return;
-
-	Ui::IsecalcUI ui;
-	QDialog* dlg = new QDialog;
-	ui.setupUi (dlg);
-
-	MakeColorComboBox (ui.cmb_col1);
-	MakeColorComboBox (ui.cmb_col2);
-
-	LDColor in1Col, in2Col;
-
-	// Run the dialog and validate input
-	forever
-	{
-		if (not dlg->exec())
-			return;
-
-		in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt();
-		in2Col = ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt();
-
-		if (in1Col == in2Col)
-		{
-			Critical ("Cannot use the same color group for both input and cutter!");
-			continue;
-		}
-
-		break;
-	}
-
-	QTemporaryFile in1dat, in2dat, outdat;
-	QString in1DATName, in2DATName, outDATName;
-
-	if (not MakeTempFile (in1dat, in1DATName) or
-		not MakeTempFile (in2dat, in2DATName) or
-		not MakeTempFile (outdat, outDATName))
-	{
-		return;
-	}
-
-	QString argv = Join (
-	{
-		in1DATName,
-		in2DATName,
-		outDATName
-	});
-
-	WriteColorGroup (in1Col, in1DATName);
-	WriteColorGroup (in2Col, in2DATName);
-	RunExtProgram (Isecalc, cfg::IsecalcPath, argv);
-	InsertOutput (outDATName, false, {});
-}
-
-// =============================================================================
-//
-void MainWindow::slot_actionEdger2()
-{
-	setlocale (LC_ALL, "C");
-
-	if (not CheckExtProgramPath (Edger2))
-		return;
-
-	QDialog* dlg = new QDialog;
-	Ui::Edger2Dialog ui;
-	ui.setupUi (dlg);
-
-	if (not dlg->exec())
-		return;
-
-	QTemporaryFile in, out;
-	QString inName, outName;
-
-	if (not MakeTempFile (in, inName) or not MakeTempFile (out, outName))
-		return;
-
-	int unmatched = ui.unmatched->currentIndex();
-
-	QString argv = Join (
-	{
-		format ("-p %1", ui.precision->value()),
-		format ("-af %1", ui.flatAngle->value()),
-		format ("-ac %1", ui.condAngle->value()),
-		format ("-ae %1", ui.edgeAngle->value()),
-		ui.delLines->isChecked()     ? "-de" : "",
-		ui.delCondLines->isChecked() ? "-dc" : "",
-		ui.colored->isChecked()      ? "-c" : "",
-		ui.bfc->isChecked()          ? "-b" : "",
-		ui.convex->isChecked()       ? "-cx" : "",
-		ui.concave->isChecked()      ? "-cv" : "",
-		unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""),
-		inName,
-		outName,
-	});
-
-	WriteSelection (inName);
-
-	if (not RunExtProgram (Edger2, cfg::Edger2Path, argv))
-		return;
-
-	InsertOutput (outName, true, {});
-}
--- a/src/mainwindow.cpp	Sun Aug 30 15:18:41 2015 +0300
+++ b/src/mainwindow.cpp	Sun Aug 30 17:20:55 2015 +0300
@@ -51,6 +51,7 @@
 #include "ui_mainwindow.h"
 #include "primitives.h"
 #include "editmodes/abstractEditMode.h"
+#include "toolsets/toolset.h"
 
 static bool g_isSelectionLocked = false;
 static QMap<QAction*, QKeySequence> g_defaultShortcuts;
@@ -127,6 +128,40 @@
 		this, SLOT (circleToolSegmentsChanged()));
 	circleToolSegmentsChanged(); // invoke it manually for initial label text
 
+	// Examine the toolsets and make a dictionary of tools
+	m_toolsets = Toolset::createToolsets (this);
+
+	QStringList ignore;
+	for (int i = 0; i < Toolset::staticMetaObject.methodCount(); ++i)
+	{
+		QMetaMethod method = Toolset::staticMetaObject.method (i);
+		ignore.append (QString::fromUtf8 (method.name()));
+	}
+
+	for (Toolset* toolset : m_toolsets)
+	{
+		const QMetaObject* meta = toolset->metaObject();
+
+		for (int i = 0; i < meta->methodCount(); ++i)
+		{
+			ToolInfo info;
+			info.method = meta->method (i);
+			info.object = toolset;
+			QString methodName = QString::fromUtf8 (info.method.name());
+
+			if (ignore.contains (methodName))
+				continue; // The method was inherited from base classes
+
+			QString actionName = "action" + methodName.left (1).toUpper() + methodName.mid (1);
+			QAction* action = findChild<QAction*> (actionName);
+
+			if (action == nullptr)
+				print ("No action for %1::%2 (looked for %3)\n", meta->className(), methodName, actionName);
+			else
+				m_toolmap[action] = info;
+		}
+	}
+
 	for (QVariant const& toolbarname : cfg::HiddenToolbars)
 	{
 		QToolBar* toolbar = findChild<QToolBar*> (toolbarname.toString());
@@ -147,8 +182,19 @@
 {
 	// Get the name of the sender object and use it to compose the slot name,
 	// then invoke this slot to call the action.
-	QMetaObject::invokeMethod (this,
-		qPrintable (format ("slot_%1", sender()->objectName())), Qt::DirectConnection);
+	QAction* action = qobject_cast<QAction*> (sender());
+
+	if (action)
+	{
+		if (m_toolmap.contains (action))
+		{
+			const ToolInfo& info = m_toolmap[action];
+			info.method.invoke (info.object, Qt::DirectConnection);
+		}
+		else
+			print ("No tool info for %1!\n", action->objectName());
+	}
+
 	endAction();
 }
 
@@ -677,14 +723,14 @@
 	contextMenu->addAction (ui.actionCut);
 	contextMenu->addAction (ui.actionCopy);
 	contextMenu->addAction (ui.actionPaste);
-	contextMenu->addAction (ui.actionDelete);
+	contextMenu->addAction (ui.actionRemove);
 	contextMenu->addSeparator();
 	contextMenu->addAction (ui.actionSetColor);
 
 	if (single)
 		contextMenu->addAction (ui.actionEditRaw);
 
-	contextMenu->addAction (ui.actionBorders);
+	contextMenu->addAction (ui.actionMakeBorders);
 	contextMenu->addAction (ui.actionSetOverlay);
 	contextMenu->addAction (ui.actionClearOverlay);
 
@@ -1024,7 +1070,7 @@
 
 	ui.actionWireframe->setChecked (cfg::DrawWireframe);
 	ui.actionAxes->setChecked (cfg::DrawAxes);
-	ui.actionBFCView->setChecked (cfg::BFCRedGreenView);
+	ui.actionBfcView->setChecked (cfg::BFCRedGreenView);
 	ui.actionRandomColors->setChecked (cfg::RandomColors);
 	ui.actionDrawAngles->setChecked (cfg::DrawAngles);
 	ui.actionDrawSurfaces->setChecked (cfg::DrawSurfaces);
--- a/src/mainwindow.h	Sun Aug 30 15:18:41 2015 +0300
+++ b/src/mainwindow.h	Sun Aug 30 17:20:55 2015 +0300
@@ -23,6 +23,7 @@
 #include <QListWidget>
 #include <QRadioButton>
 #include <QTreeWidget>
+#include <QMetaMethod>
 #include "configuration.h"
 #include "ldObject.h"
 #include "colors.h"
@@ -35,6 +36,7 @@
 class QComboBox;
 class QProgressBar;
 struct Primitive;
+class Toolset;
 
 class LDQuickColor
 {
@@ -168,112 +170,13 @@
 	void ringToolHiResClicked (bool clicked);
 	void circleToolSegmentsChanged();
 	void slot_action();
-	void slot_actionNew();
-	void slot_actionNewFile();
-	void slot_actionOpen();
-	void slot_actionDownloadFrom();
-	void slot_actionSave();
-	void slot_actionSaveAs();
-	void slot_actionSaveAll();
-	void slot_actionClose();
-	void slot_actionCloseAll();
-	void slot_actionInsertFrom();
-	void slot_actionExportTo();
-	void slot_actionSettings();
-	void slot_actionSetLDrawPath();
-	void slot_actionScanPrimitives();
-	void slot_actionExit();
-	void slot_actionResetView();
-	void slot_actionAxes();
-	void slot_actionWireframe();
-	void slot_actionBFCView();
-	void slot_actionSetOverlay();
-	void slot_actionClearOverlay();
-	void slot_actionScreenshot();
-	void slot_actionInsertRaw();
-	void slot_actionNewSubfile();
-	void slot_actionNewLine();
-	void slot_actionNewTriangle();
-	void slot_actionNewQuad();
-	void slot_actionNewCLine();
-	void slot_actionNewComment();
-	void slot_actionNewBFC();
-	void slot_actionUndo();
-	void slot_actionRedo();
-	void slot_actionCut();
-	void slot_actionCopy();
-	void slot_actionPaste();
-	void slot_actionDelete();
-	void slot_actionSelectAll();
-	void slot_actionSelectByColor();
-	void slot_actionSelectByType();
-	void slot_actionModeDraw();
-	void slot_actionModeSelect();
-	void slot_actionModeRectangle();
-	void slot_actionModeCircle();
-	void slot_actionModeMagicWand();
-	void slot_actionModeLinePath();
-	void slot_actionSetDrawDepth();
-	void slot_actionSetColor();
-	void slot_actionAutocolor();
-	void slot_actionUncolor();
-	void slot_actionInline();
-	void slot_actionInlineDeep();
-	void slot_actionInvert();
-	void slot_actionMakePrimitive();
-	void slot_actionSplitQuads();
-	void slot_actionEditRaw();
-	void slot_actionBorders();
-	void slot_actionRoundCoordinates();
-	void slot_actionVisibilityHide();
-	void slot_actionVisibilityReveal();
-	void slot_actionVisibilityToggle();
-	void slot_actionReplaceCoords();
-	void slot_actionFlip();
-	void slot_actionDemote();
-	void slot_actionYtruder();
-	void slot_actionRectifier();
-	void slot_actionIntersector();
-	void slot_actionIsecalc();
-	void slot_actionCoverer();
-	void slot_actionEdger2();
-	void slot_actionHelp();
-	void slot_actionAbout();
-	void slot_actionAboutQt();
-	void slot_actionGridCoarse();
-	void slot_actionGridMedium();
-	void slot_actionGridFine();
-	void slot_actionEdit();
-	void slot_actionMoveUp();
-	void slot_actionMoveDown();
-	void slot_actionMoveXNeg();
-	void slot_actionMoveXPos();
-	void slot_actionMoveYNeg();
-	void slot_actionMoveYPos();
-	void slot_actionMoveZNeg();
-	void slot_actionMoveZPos();
-	void slot_actionRotateXNeg();
-	void slot_actionRotateXPos();
-	void slot_actionRotateYNeg();
-	void slot_actionRotateYPos();
-	void slot_actionRotateZNeg();
-	void slot_actionRotateZPos();
-	void slot_actionRotationPoint();
-	void slot_actionAddHistoryLine();
-	void slot_actionJumpTo();
-	void slot_actionSubfileSelection();
-	void slot_actionDrawAngles();
-	void slot_actionRandomColors();
-	void slot_actionOpenSubfiles();
-	void slot_actionSplitLines();
-	void slot_actionDrawSurfaces();
-	void slot_actionDrawEdgeLines();
-	void slot_actionDrawConditionalLines();
 
 protected:
 	void closeEvent (QCloseEvent* ev);
 
 private:
+	struct ToolInfo { QMetaMethod method; Toolset* object; };
+
 	GLRenderer*			m_renderer;
 	LDObjectList		m_sel;
 	QList<LDQuickColor>	m_quickColors;
@@ -283,6 +186,8 @@
 	class Ui_MainWindow& ui;
 	QTabBar*			m_tabs;
 	bool				m_updatingTabs;
+	QVector<Toolset*>	m_toolsets;
+	QMap<QAction*, ToolInfo> m_toolmap;
 
 private slots:
 	void slot_selectionChanged();
--- a/src/mainwindow.ui	Sun Aug 30 15:18:41 2015 +0300
+++ b/src/mainwindow.ui	Sun Aug 30 17:20:55 2015 +0300
@@ -75,8 +75,8 @@
           <rect>
            <x>0</x>
            <y>0</y>
-           <width>218</width>
-           <height>124</height>
+           <width>234</width>
+           <height>402</height>
           </rect>
          </property>
          <attribute name="label">
@@ -157,8 +157,8 @@
           <rect>
            <x>0</x>
            <y>0</y>
-           <width>96</width>
-           <height>86</height>
+           <width>234</width>
+           <height>402</height>
           </rect>
          </property>
          <attribute name="label">
@@ -210,7 +210,7 @@
        <normaloff>:/icons/open-recent.png</normaloff>:/icons/open-recent.png</iconset>
      </property>
     </widget>
-    <addaction name="actionNew"/>
+    <addaction name="actionNewPart"/>
     <addaction name="actionNewFile"/>
     <addaction name="actionOpen"/>
     <addaction name="menuOpenRecent"/>
@@ -239,7 +239,7 @@
     <addaction name="actionResetView"/>
     <addaction name="actionAxes"/>
     <addaction name="actionWireframe"/>
-    <addaction name="actionBFCView"/>
+    <addaction name="actionBfcView"/>
     <addaction name="actionDrawAngles"/>
     <addaction name="actionRandomColors"/>
     <addaction name="separator"/>
@@ -261,8 +261,8 @@
     <addaction name="actionNewSubfile"/>
     <addaction name="actionNewLine"/>
     <addaction name="actionNewTriangle"/>
-    <addaction name="actionNewQuad"/>
-    <addaction name="actionNewCLine"/>
+    <addaction name="actionNewQuadrilateral"/>
+    <addaction name="actionNewConditionalLine"/>
     <addaction name="actionNewComment"/>
     <addaction name="actionNewBFC"/>
    </widget>
@@ -278,7 +278,7 @@
     <addaction name="actionCut"/>
     <addaction name="actionCopy"/>
     <addaction name="actionPaste"/>
-    <addaction name="actionDelete"/>
+    <addaction name="actionRemove"/>
     <addaction name="separator"/>
     <addaction name="actionSelectAll"/>
     <addaction name="actionSelectByColor"/>
@@ -304,7 +304,7 @@
     <addaction name="actionUncolor"/>
     <addaction name="separator"/>
     <addaction name="actionInvert"/>
-    <addaction name="actionInline"/>
+    <addaction name="actionInlineShallow"/>
     <addaction name="actionInlineDeep"/>
     <addaction name="actionMakePrimitive"/>
     <addaction name="separator"/>
@@ -312,9 +312,9 @@
     <addaction name="separator"/>
     <addaction name="actionSplitQuads"/>
     <addaction name="actionEditRaw"/>
-    <addaction name="actionBorders"/>
+    <addaction name="actionMakeBorders"/>
     <addaction name="actionRoundCoordinates"/>
-    <addaction name="actionReplaceCoords"/>
+    <addaction name="actionReplaceCoordinates"/>
     <addaction name="actionFlip"/>
     <addaction name="actionDemote"/>
     <addaction name="actionOpenSubfiles"/>
@@ -390,7 +390,7 @@
     <addaction name="menuRotate"/>
     <addaction name="menuObject_List"/>
     <addaction name="separator"/>
-    <addaction name="actionRotationPoint"/>
+    <addaction name="actionConfigureRotationPoint"/>
    </widget>
    <addaction name="menuFile"/>
    <addaction name="menuView"/>
@@ -412,7 +412,7 @@
    <attribute name="toolBarBreak">
     <bool>false</bool>
    </attribute>
-   <addaction name="actionNew"/>
+   <addaction name="actionNewPart"/>
    <addaction name="actionNewFile"/>
    <addaction name="actionOpen"/>
    <addaction name="actionSave"/>
@@ -431,8 +431,8 @@
    <addaction name="actionNewSubfile"/>
    <addaction name="actionNewLine"/>
    <addaction name="actionNewTriangle"/>
-   <addaction name="actionNewQuad"/>
-   <addaction name="actionNewCLine"/>
+   <addaction name="actionNewQuadrilateral"/>
+   <addaction name="actionNewConditionalLine"/>
    <addaction name="actionNewComment"/>
    <addaction name="actionNewBFC"/>
   </widget>
@@ -451,7 +451,7 @@
    <addaction name="actionCut"/>
    <addaction name="actionCopy"/>
    <addaction name="actionPaste"/>
-   <addaction name="actionDelete"/>
+   <addaction name="actionRemove"/>
   </widget>
   <widget class="QToolBar" name="toolBarSelect">
    <property name="windowTitle">
@@ -493,7 +493,7 @@
    </attribute>
    <addaction name="actionAxes"/>
    <addaction name="actionWireframe"/>
-   <addaction name="actionBFCView"/>
+   <addaction name="actionBfcView"/>
    <addaction name="actionRandomColors"/>
    <addaction name="actionDrawAngles"/>
   </widget>
@@ -511,10 +511,10 @@
    <addaction name="actionAutocolor"/>
    <addaction name="actionInvert"/>
    <addaction name="actionSplitQuads"/>
-   <addaction name="actionInline"/>
+   <addaction name="actionInlineShallow"/>
    <addaction name="actionEditRaw"/>
-   <addaction name="actionBorders"/>
-   <addaction name="actionReplaceCoords"/>
+   <addaction name="actionMakeBorders"/>
+   <addaction name="actionReplaceCoordinates"/>
    <addaction name="actionRoundCoordinates"/>
    <addaction name="actionVisibilityHide"/>
    <addaction name="actionVisibilityToggle"/>
@@ -565,7 +565,7 @@
    <addaction name="actionCoverer"/>
    <addaction name="actionEdger2"/>
   </widget>
-  <action name="actionNew">
+  <action name="actionNewPart">
    <property name="icon">
     <iconset resource="../ldforge.qrc">
      <normaloff>:/icons/brick.png</normaloff>:/icons/brick.png</iconset>
@@ -729,7 +729,7 @@
     <string>&amp;Wireframe</string>
    </property>
   </action>
-  <action name="actionBFCView">
+  <action name="actionBfcView">
    <property name="checkable">
     <bool>true</bool>
    </property>
@@ -803,7 +803,7 @@
     <string>Ne&amp;w Triangle</string>
    </property>
   </action>
-  <action name="actionNewQuad">
+  <action name="actionNewQuadrilateral">
    <property name="icon">
     <iconset resource="../ldforge.qrc">
      <normaloff>:/icons/add-quad.png</normaloff>:/icons/add-quad.png</iconset>
@@ -812,7 +812,7 @@
     <string>New &amp;Quadrilateral</string>
    </property>
   </action>
-  <action name="actionNewCLine">
+  <action name="actionNewConditionalLine">
    <property name="icon">
     <iconset resource="../ldforge.qrc">
      <normaloff>:/icons/add-condline.png</normaloff>:/icons/add-condline.png</iconset>
@@ -917,7 +917,7 @@
     <string>Ctrl+V</string>
    </property>
   </action>
-  <action name="actionDelete">
+  <action name="actionRemove">
    <property name="icon">
     <iconset resource="../ldforge.qrc">
      <normaloff>:/icons/delete.png</normaloff>:/icons/delete.png</iconset>
@@ -1045,7 +1045,7 @@
     <string>Reduce colors of everything selected to main and edge colors</string>
    </property>
   </action>
-  <action name="actionInline">
+  <action name="actionInlineShallow">
    <property name="icon">
     <iconset resource="../ldforge.qrc">
      <normaloff>:/icons/inline.png</normaloff>:/icons/inline.png</iconset>
@@ -1120,7 +1120,7 @@
     <string>Edit the LDraw code of this object.</string>
    </property>
   </action>
-  <action name="actionBorders">
+  <action name="actionMakeBorders">
    <property name="icon">
     <iconset resource="../ldforge.qrc">
      <normaloff>:/icons/make-borders.png</normaloff>:/icons/make-borders.png</iconset>
@@ -1156,7 +1156,7 @@
     <string>Toggles visibility/hiding on objects.</string>
    </property>
   </action>
-  <action name="actionReplaceCoords">
+  <action name="actionReplaceCoordinates">
    <property name="icon">
     <iconset resource="../ldforge.qrc">
      <normaloff>:/icons/replace-coords.png</normaloff>:/icons/replace-coords.png</iconset>
@@ -1473,7 +1473,7 @@
     <string>Ctrl+Up</string>
    </property>
   </action>
-  <action name="actionRotationPoint">
+  <action name="actionConfigureRotationPoint">
    <property name="text">
     <string>&amp;Set Rotation Point</string>
    </property>
--- a/src/partDownloader.cpp	Sun Aug 30 15:18:41 2015 +0300
+++ b/src/partDownloader.cpp	Sun Aug 30 17:20:55 2015 +0300
@@ -582,10 +582,3 @@
 {
 	networkReply()->abort();
 }
-
-// =============================================================================
-//
-void MainWindow::slot_actionDownloadFrom()
-{
-	PartDownloader::staticBegin();
-}
--- a/src/primitives.cpp	Sun Aug 30 15:18:41 2015 +0300
+++ b/src/primitives.cpp	Sun Aug 30 17:20:55 2015 +0300
@@ -689,27 +689,3 @@
 	if (on and ui->sb_segs->value() == LowResolution)
 		ui->sb_segs->setValue (HighResolution);
 }
-
-// =============================================================================
-//
-void MainWindow::slot_actionMakePrimitive()
-{
-	PrimitivePrompt* dlg = new PrimitivePrompt (g_win);
-
-	if (not dlg->exec())
-		return;
-
-	int segs = dlg->ui->sb_segs->value();
-	int divs = dlg->ui->cb_hires->isChecked() ? HighResolution : LowResolution;
-	int num = dlg->ui->sb_ringnum->value();
-	PrimitiveType type =
-		dlg->ui->rb_circle->isChecked()   ? Circle :
-		dlg->ui->rb_cylinder->isChecked() ? Cylinder :
-		dlg->ui->rb_disc->isChecked()     ? Disc :
-		dlg->ui->rb_ndisc->isChecked()    ? DiscNeg :
-		dlg->ui->rb_ring->isChecked()     ? Ring : Cone;
-
-	LDDocument* f = GeneratePrimitive (type, segs, divs, num);
-	f->setImplicit (false);
-	g_win->save (f, false);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/algorithmtoolset.cpp	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,586 @@
+/*
+ *  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 <limits>
+#include <QBoxLayout>
+#include <QCheckBox>
+#include <QDir>
+#include <QInputDialog>
+#include <QSpinBox>
+#include "../mainwindow.h"
+#include "../main.h"
+#include "../ldDocument.h"
+#include "../miscallenous.h"
+#include "../radioGroup.h"
+#include "../glRenderer.h"
+#include "../dialogs.h"
+#include "../colors.h"
+#include "../ldObjectMath.h"
+#include "../ldobjectiterator.h"
+#include "ui_replcoords.h"
+#include "ui_editraw.h"
+#include "ui_flip.h"
+#include "ui_addhistoryline.h"
+#include "algorithmtoolset.h"
+
+EXTERN_CFGENTRY (String, DefaultUser)
+
+CFGENTRY (Int, RoundPosition, 3)
+CFGENTRY (Int, RoundMatrix, 4)
+CFGENTRY (Int, SplitLinesSegments, 5)
+EXTERN_CFGENTRY (String, DefaultName)
+EXTERN_CFGENTRY (String, DefaultUser)
+EXTERN_CFGENTRY (Bool, UseCALicense)
+
+AlgorithmToolset::AlgorithmToolset (MainWindow* parent) :
+	Toolset (parent)
+{
+}
+
+void AlgorithmToolset::splitQuads()
+{
+	int num = 0;
+
+	for (LDObjectIterator<LDQuad> it (Selection()); it.isValid(); ++it)
+	{
+		// Find the index of this quad
+		int index = it->lineNumber();
+
+		if (index == -1)
+			return;
+
+		QList<LDTriangle*> triangles = it->splitToTriangles();
+
+		// Replace the quad with the first triangle and add the second triangle
+		// after the first one.
+		CurrentDocument()->setObject (index, triangles[0]);
+		CurrentDocument()->insertObj (index + 1, triangles[1]);
+		num++;
+	}
+
+	print ("%1 quadrilaterals split", num);
+}
+
+void AlgorithmToolset::editRaw()
+{
+	if (Selection().size() != 1)
+		return;
+
+	LDObject* obj = Selection()[0];
+	QDialog* dlg = new QDialog;
+	Ui::EditRawUI ui;
+
+	ui.setupUi (dlg);
+	ui.code->setText (obj->asText());
+
+	if (obj->type() == OBJ_Error)
+		ui.errorDescription->setText (static_cast<LDError*> (obj)->reason());
+	else
+	{
+		ui.errorDescription->hide();
+		ui.errorIcon->hide();
+	}
+
+	if (dlg->exec() == QDialog::Rejected)
+		return;
+
+	// Reinterpret it from the text of the input field
+	LDObject* newobj = ParseLine (ui.code->text());
+	obj->replace (newobj);
+}
+
+void AlgorithmToolset::makeBorders()
+{
+	LDObjectList objs = Selection();
+	int num = 0;
+
+	for (LDObject* obj : objs)
+	{
+		const LDObjectType type = obj->type();
+
+		if (type != OBJ_Quad and type != OBJ_Triangle)
+			continue;
+
+		LDLine* lines[4];
+
+		if (type == OBJ_Quad)
+		{
+			LDQuad* quad = static_cast<LDQuad*> (obj);
+			lines[0] = LDSpawn<LDLine> (quad->vertex (0), quad->vertex (1));
+			lines[1] = LDSpawn<LDLine> (quad->vertex (1), quad->vertex (2));
+			lines[2] = LDSpawn<LDLine> (quad->vertex (2), quad->vertex (3));
+			lines[3] = LDSpawn<LDLine> (quad->vertex (3), quad->vertex (0));
+		}
+		else
+		{
+			LDTriangle* tri = static_cast<LDTriangle*> (obj);
+			lines[0] = LDSpawn<LDLine> (tri->vertex (0), tri->vertex (1));
+			lines[1] = LDSpawn<LDLine> (tri->vertex (1), tri->vertex (2));
+			lines[2] = LDSpawn<LDLine> (tri->vertex (2), tri->vertex (0));
+			lines[3] = nullptr;
+		}
+
+		for (int i = 0; i < countof (lines); ++i)
+		{
+			if (lines[i] == null)
+				continue;
+
+			long idx = obj->lineNumber() + i + 1;
+			CurrentDocument()->insertObj (idx, lines[i]);
+			++num;
+		}
+	}
+
+	print (tr ("Added %1 border lines"), num);
+}
+
+void AlgorithmToolset::roundCoordinates()
+{
+	setlocale (LC_ALL, "C");
+	int num = 0;
+
+	for (LDObject* obj : Selection())
+	{
+		LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
+
+		if (mo != null)
+		{
+			Vertex v = mo->position();
+			Matrix t = mo->transform();
+
+			// Note: matrix values are to be rounded to 4 decimals.
+			v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
+			ApplyToMatrix (t, [](int, double& a) { RoundToDecimals (a, cfg::RoundMatrix); });
+
+			mo->setPosition (v);
+			mo->setTransform (t);
+			num += 12;
+		}
+		else
+		{
+			for (int i = 0; i < obj->numVertices(); ++i)
+			{
+				Vertex v = obj->vertex (i);
+				v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
+				obj->setVertex (i, v);
+				num += 3;
+			}
+		}
+	}
+
+	print (tr ("Rounded %1 values"), num);
+	m_window->refreshObjectList();
+}
+
+void AlgorithmToolset::replaceCoordinates()
+{
+	QDialog* dlg = new QDialog (m_window);
+	Ui::ReplaceCoordsUI ui;
+	ui.setupUi (dlg);
+
+	if (not dlg->exec())
+		return;
+
+	const double search = ui.search->value(),
+		replacement = ui.replacement->value();
+	const bool any = ui.any->isChecked(),
+		rel = ui.relative->isChecked();
+
+	QList<Axis> sel;
+	int num = 0;
+
+	if (ui.x->isChecked()) sel << X;
+	if (ui.y->isChecked()) sel << Y;
+	if (ui.z->isChecked()) sel << Z;
+
+	for (LDObject* obj : Selection())
+	{
+		for (int i = 0; i < obj->numVertices(); ++i)
+		{
+			Vertex v = obj->vertex (i);
+
+			v.apply ([&](Axis ax, double& coord)
+			{
+				if (not sel.contains (ax) or
+					(not any and coord != search))
+				{
+					return;
+				}
+
+				if (not rel)
+					coord = 0;
+
+				coord += replacement;
+				num++;
+			});
+
+			obj->setVertex (i, v);
+		}
+	}
+
+	print (tr ("Altered %1 values"), num);
+}
+
+void AlgorithmToolset::flip()
+{
+	QDialog* dlg = new QDialog;
+	Ui::FlipUI ui;
+	ui.setupUi (dlg);
+
+	if (not dlg->exec())
+		return;
+
+	QList<Axis> sel;
+
+	if (ui.x->isChecked()) sel << X;
+	if (ui.y->isChecked()) sel << Y;
+	if (ui.z->isChecked()) sel << Z;
+
+	for (LDObject* obj : Selection())
+	{
+		for (int i = 0; i < obj->numVertices(); ++i)
+		{
+			Vertex v = obj->vertex (i);
+
+			v.apply ([&](Axis ax, double& a)
+			{
+				if (sel.contains (ax))
+					a = -a;
+			});
+
+			obj->setVertex (i, v);
+		}
+	}
+}
+
+void AlgorithmToolset::demote()
+{
+	int num = 0;
+
+	for (LDObjectIterator<LDCondLine> it (Selection()); it.isValid(); ++it)
+	{
+		it->toEdgeLine();
+		++num;
+	}
+
+	print (tr ("Converted %1 conditional lines"), num);
+}
+
+bool AlgorithmToolset::isColorUsed (LDColor color) const
+{
+	for (LDObject* obj : CurrentDocument()->objects())
+	{
+		if (obj->isColored() and obj->color() == color)
+			return true;
+	}
+
+	return false;
+}
+
+void AlgorithmToolset::autocolor()
+{
+	LDColor color;
+
+	for (color = 0; color.isLDConfigColor(); ++color)
+	{
+		if (color.isValid() and not isColorUsed (color))
+			break;
+	}
+
+	if (not color.isLDConfigColor())
+	{
+		print (tr ("Cannot auto-color: all colors are in use!"));
+		return;
+	}
+
+	for (LDObject* obj : Selection())
+	{
+		if (not obj->isColored())
+			continue;
+
+		obj->setColor (color);
+	}
+
+	print (tr ("Auto-colored: new color is [%1] %2"), color.index(), color.name());
+}
+
+void AlgorithmToolset::addHistoryLine()
+{
+	LDObject* obj;
+	bool ishistory = false;
+	bool prevIsHistory = false;
+
+	QDialog* dlg = new QDialog;
+	Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
+	ui->setupUi (dlg);
+	ui->m_username->setText (cfg::DefaultUser);
+	ui->m_date->setDate (QDate::currentDate());
+	ui->m_comment->setFocus();
+
+	if (not dlg->exec())
+		return;
+
+	// Create the comment object based on input
+	LDComment* comment = new LDComment (format ("!HISTORY %1 [%2] %3",
+		ui->m_date->date().toString ("yyyy-MM-dd"),
+		ui->m_username->text(),
+		ui->m_comment->text()));
+
+	// Find a spot to place the new comment
+	for (obj = CurrentDocument()->getObject (0);
+		obj and obj->next() and not obj->next()->isScemantic();
+		obj = obj->next())
+	{
+		LDComment* comment = dynamic_cast<LDComment*> (obj);
+
+		if (comment and comment->text().startsWith ("!HISTORY "))
+			ishistory = true;
+
+		if (prevIsHistory and not ishistory)
+			break; // Last line was history, this isn't, thus insert the new history line here.
+
+		prevIsHistory = ishistory;
+	}
+
+	int idx = obj ? obj->lineNumber() : 0;
+	CurrentDocument()->insertObj (idx++, comment);
+
+	// If we're adding a history line right before a scemantic object, pad it
+	// an empty line
+	if (obj and obj->next() and obj->next()->isScemantic())
+		CurrentDocument()->insertObj (idx, new LDEmpty);
+
+	m_window->buildObjList();
+	delete ui;
+}
+
+void AlgorithmToolset::splitLines()
+{
+	bool ok;
+	int segments = QInputDialog::getInt (m_window, APPNAME, "Amount of segments:", cfg::SplitLinesSegments, 0,
+		std::numeric_limits<int>::max(), 1, &ok);
+
+	if (not ok)
+		return;
+
+	cfg::SplitLinesSegments = segments;
+
+	for (LDObject* obj : Selection())
+	{
+		if (not isOneOf (obj->type(), OBJ_Line, OBJ_CondLine))
+			continue;
+
+		QVector<LDObject*> newsegs;
+
+		for (int i = 0; i < segments; ++i)
+		{
+			LDObject* segment;
+			Vertex v0, v1;
+
+			v0.apply ([&](Axis ax, double& a)
+			{
+				double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
+				a = (obj->vertex (0)[ax] + ((len * i) / segments));
+			});
+
+			v1.apply ([&](Axis ax, double& a)
+			{
+				double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
+				a = (obj->vertex (0)[ax] + ((len * (i + 1)) / segments));
+			});
+
+			if (obj->type() == OBJ_Line)
+				segment = LDSpawn<LDLine> (v0, v1);
+			else
+				segment = LDSpawn<LDCondLine> (v0, v1, obj->vertex (2), obj->vertex (3));
+
+			newsegs << segment;
+		}
+
+		int ln = obj->lineNumber();
+
+		for (LDObject* seg : newsegs)
+			CurrentDocument()->insertObj (ln++, seg);
+
+		obj->destroy();
+	}
+
+	m_window->buildObjList();
+	m_window->refresh();
+}
+
+void AlgorithmToolset::subfileSelection()
+{
+	if (Selection().size() == 0)
+		return;
+
+	QString			parentpath (CurrentDocument()->fullPath());
+
+	// BFC type of the new subfile - it shall inherit the BFC type of the parent document
+	BFCStatement	bfctype (BFCStatement::NoCertify);
+
+	// Dirname of the new subfile
+	QString			subdirname (Dirname (parentpath));
+
+	// Title of the new subfile
+	QString			subtitle;
+
+	// Comment containing the title of the parent document
+	LDComment*	titleobj = dynamic_cast<LDComment*> (CurrentDocument()->getObject (0));
+
+	// License text for the subfile
+	QString			license (PreferredLicenseText());
+
+	// LDraw code body of the new subfile (i.e. code of the selection)
+	QStringList		code;
+
+	// Full path of the subfile to be
+	QString			fullsubname;
+
+	// Where to insert the subfile reference?
+	int				refidx (Selection()[0]->lineNumber());
+
+	// Determine title of subfile
+	if (titleobj != null)
+		subtitle = "~" + titleobj->text();
+	else
+		subtitle = "~subfile";
+
+	// Remove duplicate tildes
+	while (subtitle.startsWith ("~~"))
+		subtitle.remove (0, 1);
+
+	// If this the parent document isn't already in s/, we need to stuff it into
+	// a subdirectory named s/. Ensure it exists!
+	QString topdirname = Basename (Dirname (CurrentDocument()->fullPath()));
+
+	if (topdirname != "s")
+	{
+		QString desiredPath = subdirname + "/s";
+		QString title = tr ("Create subfile directory?");
+		QString text = format (tr ("The directory <b>%1</b> is suggested for "
+			"subfiles. This directory does not exist, create it?"), desiredPath);
+
+		if (QDir (desiredPath).exists() or Confirm (title, text))
+		{
+			subdirname = desiredPath;
+			QDir().mkpath (subdirname);
+		}
+		else
+			return;
+	}
+
+	// Determine the body of the name of the subfile
+	if (not parentpath.isEmpty())
+	{
+		// Chop existing '.dat' suffix
+		if (parentpath.endsWith (".dat"))
+			parentpath.chop (4);
+
+		// Remove the s?? suffix if it's there, otherwise we'll get filenames
+		// like s01s01.dat when subfiling subfiles.
+		QRegExp subfilesuffix ("s[0-9][0-9]$");
+		if (subfilesuffix.indexIn (parentpath) != -1)
+			parentpath.chop (subfilesuffix.matchedLength());
+
+		int subidx = 1;
+		QString digits;
+
+		// Now find the appropriate filename. Increase the number of the subfile
+		// until we find a name which isn't already taken.
+		do
+		{
+			digits.setNum (subidx++);
+
+			// pad it with a zero
+			if (digits.length() == 1)
+				digits.prepend ("0");
+
+			fullsubname = subdirname + "/" + Basename (parentpath) + "s" + digits + ".dat";
+		} while (FindDocument ("s\\" + Basename (fullsubname)) != null or QFile (fullsubname).exists());
+	}
+
+	// Determine the BFC winding type used in the main document - it is to
+	// be carried over to the subfile.
+	for (LDObjectIterator<LDBFC> it (CurrentDocument()); it.isValid(); ++it)
+	{
+		if (isOneOf (it->statement(), BFCStatement::CertifyCCW, BFCStatement::CertifyCW, BFCStatement::NoCertify))
+		{
+			bfctype = it->statement();
+			break;
+		}
+	}
+
+	// Get the body of the document in LDraw code
+	for (LDObject* obj : Selection())
+		code << obj->asText();
+
+	// Create the new subfile document
+	LDDocument* doc = LDDocument::createNew();
+	doc->setImplicit (false);
+	doc->setFullPath (fullsubname);
+	doc->setName (LDDocument::shortenName (fullsubname));
+
+	LDObjectList objs;
+	objs << LDSpawn<LDComment> (subtitle);
+	objs << LDSpawn<LDComment> ("Name: "); // This gets filled in when the subfile is saved
+	objs << LDSpawn<LDComment> (format ("Author: %1 [%2]", cfg::DefaultName, cfg::DefaultUser));
+	objs << LDSpawn<LDComment> ("!LDRAW_ORG Unofficial_Subpart");
+
+	if (not license.isEmpty())
+		objs << LDSpawn<LDComment> (license);
+
+	objs << LDSpawn<LDEmpty>();
+	objs << LDSpawn<LDBFC> (bfctype);
+	objs << LDSpawn<LDEmpty>();
+
+	doc->addObjects (objs);
+
+	// Add the actual subfile code to the new document
+	for (QString line : code)
+	{
+		LDObject* obj = ParseLine (line);
+		doc->addObject (obj);
+	}
+
+	// Try save it
+	if (m_window->save (doc, true))
+	{
+		// Save was successful. Delete the original selection now from the
+		// main document.
+		for (LDObject* obj : Selection())
+			obj->destroy();
+
+		// Add a reference to the new subfile to where the selection was
+		LDSubfile* ref = LDSpawn<LDSubfile>();
+		ref->setColor (MainColor);
+		ref->setFileInfo (doc);
+		ref->setPosition (Origin);
+		ref->setTransform (IdentityMatrix);
+		CurrentDocument()->insertObj (refidx, ref);
+
+		// Refresh stuff
+		m_window->updateDocumentList();
+		m_window->doFullRefresh();
+	}
+	else
+	{
+		// Failed to save.
+		doc->dismiss();
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/algorithmtoolset.h	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,42 @@
+/*
+ *  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/>.
+ */
+
+#pragma once
+#include "toolset.h"
+
+class AlgorithmToolset : public Toolset
+{
+	Q_OBJECT
+public:
+	explicit AlgorithmToolset (MainWindow* parent);
+
+	Q_INVOKABLE void addHistoryLine();
+	Q_INVOKABLE void autocolor();
+	Q_INVOKABLE void demote();
+	Q_INVOKABLE void editRaw();
+	Q_INVOKABLE void flip();
+	Q_INVOKABLE void makeBorders();
+	Q_INVOKABLE void replaceCoordinates();
+	Q_INVOKABLE void roundCoordinates();
+	Q_INVOKABLE void splitLines();
+	Q_INVOKABLE void splitQuads();
+	Q_INVOKABLE void subfileSelection();
+
+private:
+	bool isColorUsed (class LDColor color) const;
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/basictoolset.cpp	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,299 @@
+/*
+ *  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 <QApplication>
+#include <QClipboard>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QTextEdit>
+#include <QVBoxLayout>
+#include "../addObjectDialog.h"
+#include "../glRenderer.h"
+#include "../ldDocument.h"
+#include "../ldObject.h"
+#include "../ldobjectiterator.h"
+#include "../mainwindow.h"
+#include "../dialogs/colorselector.h"
+#include "basictoolset.h"
+
+BasicToolset::BasicToolset (MainWindow *parent) :
+	Toolset (parent) {}
+
+static int CopyToClipboard()
+{
+	LDObjectList objs = Selection();
+	int num = 0;
+
+	// Clear the clipboard first.
+	qApp->clipboard()->clear();
+
+	// Now, copy the contents into the clipboard.
+	QString data;
+
+	for (LDObject* obj : objs)
+	{
+		if (not data.isEmpty())
+			data += "\n";
+
+		data += obj->asText();
+		++num;
+	}
+
+	qApp->clipboard()->setText (data);
+	return num;
+}
+
+void BasicToolset::cut()
+{
+	int num = CopyToClipboard();
+	m_window->deleteSelection();
+	print (tr ("%1 objects cut"), num);
+}
+
+void BasicToolset::copy()
+{
+	int num = CopyToClipboard();
+	print (tr ("%1 objects copied"), num);
+}
+
+void BasicToolset::paste()
+{
+	const QString clipboardText = qApp->clipboard()->text();
+	int idx = m_window->getInsertionPoint();
+	CurrentDocument()->clearSelection();
+	int num = 0;
+
+	for (QString line : clipboardText.split ("\n"))
+	{
+		LDObject* pasted = ParseLine (line);
+		CurrentDocument()->insertObj (idx++, pasted);
+		pasted->select();
+		++num;
+	}
+
+	print (tr ("%1 objects pasted"), num);
+	m_window->refresh();
+	m_window->scrollToSelection();
+}
+
+void BasicToolset::remove()
+{
+	int num = m_window->deleteSelection();
+	print (tr ("%1 objects deleted"), num);
+}
+
+void BasicToolset::doInline (bool deep)
+{
+	for (LDObjectIterator<LDSubfile> it (Selection()); it.isValid(); ++it)
+	{
+		// Get the index of the subfile so we know where to insert the
+		// inlined contents.
+		int idx = it->lineNumber();
+
+		if (idx != -1)
+		{
+			LDObjectList objs = it->inlineContents (deep, false);
+	
+			// Merge in the inlined objects
+			for (LDObject* inlineobj : objs)
+			{
+				QString line = inlineobj->asText();
+				inlineobj->destroy();
+				LDObject* newobj = ParseLine (line);
+				CurrentDocument()->insertObj (idx++, newobj);
+				newobj->select();
+			}
+	
+			// Delete the subfile now as it's been inlined.
+			it->destroy();
+		}
+	}
+}
+
+void BasicToolset::inlineShallow()
+{
+	doInline (false);
+}
+
+void BasicToolset::inlineDeep()
+{
+	doInline (true);
+}
+
+void BasicToolset::undo()
+{
+	CurrentDocument()->undo();
+}
+
+void BasicToolset::redo()
+{
+	CurrentDocument()->redo();
+}
+
+void BasicToolset::uncolor()
+{
+	int num = 0;
+
+	for (LDObject* obj : Selection())
+	{
+		if (not obj->isColored())
+			continue;
+
+		obj->setColor (obj->defaultColor());
+		num++;
+	}
+
+	print (tr ("%1 objects uncolored"), num);
+}
+
+void BasicToolset::insertRaw()
+{
+	int idx = m_window->getInsertionPoint();
+
+	QDialog* const dlg = new QDialog;
+	QVBoxLayout* const layout = new QVBoxLayout;
+	QTextEdit* const inputbox = new QTextEdit;
+	QDialogButtonBox* const buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+
+	layout->addWidget (inputbox);
+	layout->addWidget (buttons);
+	dlg->setLayout (layout);
+	dlg->setWindowTitle (APPNAME " - Insert Raw");
+	dlg->connect (buttons, SIGNAL (accepted()), dlg, SLOT (accept()));
+	dlg->connect (buttons, SIGNAL (rejected()), dlg, SLOT (reject()));
+
+	if (dlg->exec() == QDialog::Rejected)
+		return;
+
+	CurrentDocument()->clearSelection();
+
+	for (QString line : QString (inputbox->toPlainText()).split ("\n"))
+	{
+		LDObject* obj = ParseLine (line);
+
+		CurrentDocument()->insertObj (idx, obj);
+		obj->select();
+		idx++;
+	}
+
+	m_window->refresh();
+	m_window->scrollToSelection();
+}
+
+void BasicToolset::setColor()
+{
+	if (Selection().isEmpty())
+		return;
+
+	LDObjectList objs = Selection();
+
+	// If all selected objects have the same color, said color is our default
+	// value to the color selection dialog.
+	LDColor color;
+	LDColor defaultcol = m_window->getSelectedColor();
+
+	// Show the dialog to the user now and ask for a color.
+	if (ColorSelector::selectColor (color, defaultcol, m_window))
+	{
+		for (LDObject* obj : objs)
+		{
+			if (obj->isColored())
+				obj->setColor (color);
+		}
+	}
+}
+
+void BasicToolset::invert()
+{
+	for (LDObject* obj : Selection())
+		obj->invert();
+}
+
+void BasicToolset::newSubfile()
+{
+	AddObjectDialog::staticDialog (OBJ_Subfile, nullptr);
+}
+
+void BasicToolset::newLine()
+{
+	AddObjectDialog::staticDialog (OBJ_Line, nullptr);
+}
+
+void BasicToolset::newTriangle()
+{
+	AddObjectDialog::staticDialog (OBJ_Triangle, nullptr);
+}
+
+void BasicToolset::newQuadrilateral()
+{
+	AddObjectDialog::staticDialog (OBJ_Quad, nullptr);
+}
+
+void BasicToolset::newConditionalLine()
+{
+	AddObjectDialog::staticDialog (OBJ_CondLine, nullptr);
+}
+
+void BasicToolset::newComment()
+{
+	AddObjectDialog::staticDialog (OBJ_Comment, nullptr);
+}
+
+void BasicToolset::newBFC()
+{
+	AddObjectDialog::staticDialog (OBJ_BFC, nullptr);
+}
+
+void BasicToolset::edit()
+{
+	if (Selection().size() != 1)
+		return;
+
+	LDObject* obj = Selection().first();
+	AddObjectDialog::staticDialog (obj->type(), obj);
+}
+
+void BasicToolset::modeSelect()
+{
+	m_window->R()->setEditMode (EditModeType::Select);
+}
+
+void BasicToolset::modeDraw()
+{
+	m_window->R()->setEditMode (EditModeType::Draw);
+}
+
+void BasicToolset::modeRectangle()
+{
+	m_window->R()->setEditMode (EditModeType::Rectangle);
+}
+
+void BasicToolset::modeCircle()
+{
+	m_window->R()->setEditMode (EditModeType::Circle);
+}
+
+void BasicToolset::modeMagicWand()
+{
+ 	m_window->R()->setEditMode (EditModeType::MagicWand);
+}
+
+void BasicToolset::modeLinePath()
+{
+	m_window->R()->setEditMode (EditModeType::LinePath);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/basictoolset.h	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,58 @@
+/*
+ *  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/>.
+ */
+
+#pragma once
+#include "toolset.h"
+
+class BasicToolset : public Toolset
+{
+	Q_OBJECT
+
+public:
+	BasicToolset (MainWindow *parent);
+
+	Q_INVOKABLE void copy();
+	Q_INVOKABLE void cut();
+	Q_INVOKABLE void edit();
+	Q_INVOKABLE void inlineDeep();
+	Q_INVOKABLE void inlineShallow();
+	Q_INVOKABLE void insertRaw();
+	Q_INVOKABLE void invert();
+	Q_INVOKABLE void modeCircle();
+	Q_INVOKABLE void modeDraw();
+	Q_INVOKABLE void modeLinePath();
+	Q_INVOKABLE void modeMagicWand();
+	Q_INVOKABLE void modeRectangle();
+	Q_INVOKABLE void modeSelect();
+	Q_INVOKABLE void newBFC();
+	Q_INVOKABLE void newComment();
+	Q_INVOKABLE void newConditionalLine();
+	Q_INVOKABLE void newLine();
+	Q_INVOKABLE void newQuadrilateral();
+	Q_INVOKABLE void newSubfile();
+	Q_INVOKABLE void newTriangle();
+	Q_INVOKABLE void paste();
+	Q_INVOKABLE void redo();
+	Q_INVOKABLE void remove();
+	Q_INVOKABLE void setColor();
+	Q_INVOKABLE void uncolor();
+	Q_INVOKABLE void undo();
+
+private:
+	void doInline (bool deep);
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/extprogramtoolset.cpp	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,725 @@
+/*
+ *  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 <QProcess>
+#include <QTemporaryFile>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QSpinBox>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QGridLayout>
+#include <QFileInfo>
+#include "../main.h"
+#include "../configuration.h"
+#include "../miscallenous.h"
+#include "../mainwindow.h"
+#include "../ldDocument.h"
+#include "../radioGroup.h"
+#include "../editHistory.h"
+#include "../dialogs.h"
+#include "extprogramtoolset.h"
+#include "ui_ytruder.h"
+#include "ui_intersector.h"
+#include "ui_rectifier.h"
+#include "ui_coverer.h"
+#include "ui_isecalc.h"
+#include "ui_edger2.h"
+
+enum ExtProgramType
+{
+	Isecalc,
+	Intersector,
+	Coverer,
+	Ytruder,
+	Rectifier,
+	Edger2,
+};
+
+// =============================================================================
+//
+CFGENTRY (String, IsecalcPath, "")
+CFGENTRY (String, IntersectorPath, "")
+CFGENTRY (String, CovererPath, "")
+CFGENTRY (String, YtruderPath, "")
+CFGENTRY (String, RectifierPath, "")
+CFGENTRY (String, Edger2Path, "")
+
+QString* const g_extProgPaths[] =
+{
+	&cfg::IsecalcPath,
+	&cfg::IntersectorPath,
+	&cfg::CovererPath,
+	&cfg::YtruderPath,
+	&cfg::RectifierPath,
+	&cfg::Edger2Path,
+};
+
+CFGENTRY (Bool, IsecalcUsesWine, false)
+CFGENTRY (Bool, IntersectorUsesWine, false)
+CFGENTRY (Bool, CovererUsesWine, false)
+CFGENTRY (Bool, YtruderUsesWine, false)
+CFGENTRY (Bool, RectifierUsesWine, false)
+CFGENTRY (Bool, Edger2UsesWine, false)
+
+bool* const g_extProgWine[] =
+{
+	&cfg::IsecalcUsesWine,
+	&cfg::IntersectorUsesWine,
+	&cfg::CovererUsesWine,
+	&cfg::YtruderUsesWine,
+	&cfg::RectifierUsesWine,
+	&cfg::Edger2UsesWine,
+};
+
+const char* g_extProgNames[] =
+{
+	"Isecalc",
+	"Intersector",
+	"Coverer",
+	"Ytruder",
+	"Rectifier",
+	"Edger2"
+};
+
+ExtProgramToolset::ExtProgramToolset (MainWindow* parent) :
+	Toolset (parent) {}
+
+// =============================================================================
+//
+static bool MakeTempFile (QTemporaryFile& tmp, QString& fname)
+{
+	if (not tmp.open())
+		return false;
+
+	fname = tmp.fileName();
+	tmp.close();
+	return true;
+}
+
+// =============================================================================
+//
+static bool CheckExtProgramPath (ExtProgramType program)
+{
+	QString& path = *g_extProgPaths[program];
+
+	if (not path.isEmpty())
+		return true;
+
+	ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[program]);
+
+	if (dlg->exec() and not dlg->getPath().isEmpty())
+	{
+		path = dlg->getPath();
+		return true;
+	}
+
+	return false;
+}
+
+// =============================================================================
+//
+static QString ProcessExtProgError (ExtProgramType prog, QProcess& proc)
+{
+	switch (proc.error())
+	{
+	case QProcess::FailedToStart:
+		{
+			QString winemessage;
+
+#ifndef _WIN32
+			if (*g_extProgWine[prog])
+				winemessage = "make sure Wine is installed and ";
+#else
+			Q_UNUSED (prog);
+#endif
+
+			return format ("Program failed to start, %1check your permissions", winemessage);
+		} break;
+
+	case QProcess::Crashed:
+		return "Crashed.";
+
+	case QProcess::WriteError:
+	case QProcess::ReadError:
+		return "I/O error.";
+
+	case QProcess::UnknownError:
+		return "Unknown error";
+
+	case QProcess::Timedout:
+		return "Timed out (30 seconds)";
+	}
+
+	return "";
+}
+
+// =============================================================================
+//
+static void WriteObjects (const LDObjectList& objects, QFile& f)
+{
+	for (LDObject* obj : objects)
+	{
+		if (obj->type() == OBJ_Subfile)
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			LDObjectList objs = ref->inlineContents (true, false);
+
+			WriteObjects (objs, f);
+
+			for (LDObject* obj : objs)
+				obj->destroy();
+		}
+		else
+			f.write ((obj->asText() + "\r\n").toUtf8());
+	}
+}
+
+// =============================================================================
+//
+static void WriteObjects (const LDObjectList& objects, QString fname)
+{
+	// Write the input file
+	QFile f (fname);
+
+	if (not f.open (QIODevice::WriteOnly | QIODevice::Text))
+	{
+		Critical (format ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString()));
+		return;
+	}
+
+	WriteObjects (objects, f);
+	f.close();
+
+#ifdef DEBUG
+	QFile::copy (fname, "debug_lastInput");
+#endif
+}
+
+// =============================================================================
+//
+void WriteSelection (QString fname)
+{
+	WriteObjects (Selection(), fname);
+}
+
+// =============================================================================
+//
+void WriteColorGroup (LDColor color, QString fname)
+{
+	LDObjectList objects;
+
+	for (LDObject* obj : CurrentDocument()->objects())
+	{
+		if (not obj->isColored() or obj->color() != color)
+			continue;
+
+		objects << obj;
+	}
+
+	WriteObjects (objects, fname);
+}
+
+// =============================================================================
+//
+bool RunExtProgram (ExtProgramType prog, QString path, QString argvstr)
+{
+	QTemporaryFile input;
+	QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
+
+#ifndef _WIN32
+	if (*g_extProgWine[prog])
+	{
+		argv.insert (0, path);
+		path = "wine";
+	}
+#endif // _WIN32
+
+	print ("Running command: %1 %2\n", path, argv.join (" "));
+
+	if (not input.open())
+		return false;
+
+	QProcess proc;
+
+	// Begin!
+	proc.setStandardInputFile (input.fileName());
+	proc.start (path, argv);
+
+	if (not proc.waitForStarted())
+	{
+		Critical (format ("Couldn't start %1: %2\n", g_extProgNames[prog], ProcessExtProgError (prog, proc)));
+		return false;
+	}
+
+	// Write an enter, the utility tools all expect one
+	input.write ("\n");
+
+	// Wait while it runs
+	proc.waitForFinished();
+
+	QString err = "";
+
+	if (proc.exitStatus() != QProcess::NormalExit)
+		err = ProcessExtProgError (prog, proc);
+
+	// Check the return code
+	if (proc.exitCode() != 0)
+		err = format ("Program exited abnormally (return code %1).",  proc.exitCode());
+
+	if (not err.isEmpty())
+	{
+		Critical (format ("%1 failed: %2\n", g_extProgNames[prog], err));
+		QString filename ("externalProgramOutput.txt");
+		QFile file (filename);
+
+		if (file.open (QIODevice::WriteOnly | QIODevice::Text))
+		{
+			file.write (proc.readAllStandardOutput());
+			file.write (proc.readAllStandardError());
+			print ("Wrote output and error logs to %1", QFileInfo (file).absoluteFilePath());
+		}
+		else
+		{
+			print ("Couldn't open %1 for writing: %2",
+				QFileInfo (filename).absoluteFilePath(), file.errorString());
+		}
+
+		return false;
+	}
+
+	return true;
+}
+
+// =============================================================================
+//
+static void InsertOutput (QString fname, bool replace, QList<LDColor> colorsToReplace)
+{
+#ifdef DEBUG
+	QFile::copy (fname, "./debug_lastOutput");
+#endif // RELEASE
+
+	// Read the output file
+	QFile f (fname);
+
+	if (not f.open (QIODevice::ReadOnly))
+	{
+		Critical (format ("Couldn't open temporary file %1 for reading.\n", fname));
+		return;
+	}
+
+	LDObjectList objs = LoadFileContents (&f, null);
+
+	// If we replace the objects, delete the selection now.
+	if (replace)
+		g_win->deleteSelection();
+
+	for (LDColor color : colorsToReplace)
+		g_win->deleteByColor (color);
+
+	// Insert the new objects
+	CurrentDocument()->clearSelection();
+
+	for (LDObject* obj : objs)
+	{
+		if (not obj->isScemantic())
+		{
+			obj->destroy();
+			continue;
+		}
+
+		CurrentDocument()->addObject (obj);
+		obj->select();
+	}
+
+	g_win->doFullRefresh();
+}
+
+// =============================================================================
+// Interface for Ytruder
+// =============================================================================
+void ExtProgramToolset::ytruder()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Ytruder))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::YtruderUI ui;
+	ui.setupUi (dlg);
+
+	if (not dlg->exec())
+		return;
+
+	// Read the user's choices
+	const enum { Distance, Symmetry, Projection, Radial } mode =
+		ui.mode_distance->isChecked()   ? Distance :
+		ui.mode_symmetry->isChecked()   ? Symmetry :
+		ui.mode_projection->isChecked() ? Projection : Radial;
+
+	const Axis axis =
+		ui.axis_x->isChecked() ? X :
+		ui.axis_y->isChecked() ? Y : Z;
+
+	const double depth = ui.planeDepth->value(),
+				 condAngle = ui.condAngle->value();
+
+	QTemporaryFile indat, outdat;
+	QString inDATName, outDATName;
+
+	// Make temp files for the input and output files
+	if (not MakeTempFile (indat, inDATName) or not MakeTempFile (outdat, outDATName))
+		return;
+
+	// Compose the command-line arguments
+	QString argv = Join (
+	{
+		(axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
+		(mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
+		depth,
+		"-a",
+		condAngle,
+		inDATName,
+		outDATName
+	});
+
+	WriteSelection (inDATName);
+
+	if (not RunExtProgram (Ytruder, cfg::YtruderPath, argv))
+		return;
+
+	InsertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+// Rectifier interface
+// =============================================================================
+void ExtProgramToolset::rectifier()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Rectifier))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::RectifierUI ui;
+	ui.setupUi (dlg);
+
+	if (not dlg->exec())
+		return;
+
+	QTemporaryFile indat, outdat;
+	QString inDATName, outDATName;
+
+	// Make temp files for the input and output files
+	if (not MakeTempFile (indat, inDATName) or not MakeTempFile (outdat, outDATName))
+		return;
+
+	// Compose arguments
+	QString argv = Join (
+	{
+		(not ui.cb_condense->isChecked()) ? "-q" : "",
+		(not ui.cb_subst->isChecked()) ? "-r" : "",
+		(ui.cb_condlineCheck->isChecked()) ? "-a" : "",
+		(ui.cb_colorize->isChecked()) ? "-c" : "",
+		"-t",
+		ui.dsb_coplthres->value(),
+		inDATName,
+		outDATName
+	});
+
+	WriteSelection (inDATName);
+
+	if (not RunExtProgram (Rectifier, cfg::RectifierPath, argv))
+		return;
+
+	InsertOutput (outDATName, true, {});
+}
+
+// =============================================================================
+// Intersector interface
+// =============================================================================
+void ExtProgramToolset::intersector()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Intersector))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::IntersectorUI ui;
+	ui.setupUi (dlg);
+
+	MakeColorComboBox (ui.cmb_incol);
+	MakeColorComboBox (ui.cmb_cutcol);
+	ui.cb_repeat->setWhatsThis ("If this is set, " APPNAME " runs Intersector a second time with inverse files to cut the "
+								" cutter group with the input group. Both groups are cut by the intersection.");
+	ui.cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection.");
+
+	LDColor inCol, cutCol;
+	const bool repeatInverse = ui.cb_repeat->isChecked();
+
+	forever
+	{
+		if (not dlg->exec())
+			return;
+
+		inCol = ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt();
+		cutCol = ui.cmb_cutcol->itemData (ui.cmb_cutcol->currentIndex()).toInt();
+
+		if (inCol == cutCol)
+		{
+			Critical ("Cannot use the same color group for both input and cutter!");
+			continue;
+		}
+
+		break;
+	}
+
+	// Five temporary files!
+	// indat = input group file
+	// cutdat = cutter group file
+	// outdat = primary output
+	// outdat2 = inverse output
+	// edgesdat = edges output (isecalc)
+	QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat;
+	QString inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
+
+	if (not MakeTempFile (indat, inDATName) or
+		not MakeTempFile (cutdat, cutDATName) or
+		not MakeTempFile (outdat, outDATName) or
+		not MakeTempFile (outdat2, outDAT2Name) or
+		not MakeTempFile (edgesdat, edgesDATName))
+	{
+		return;
+	}
+
+	QString parms = Join (
+	{
+		(ui.cb_colorize->isChecked()) ? "-c" : "",
+		(ui.cb_nocondense->isChecked()) ? "-t" : "",
+		"-s",
+		ui.dsb_prescale->value()
+	});
+
+	QString argv_normal = Join (
+	{
+		parms,
+		inDATName,
+		cutDATName,
+		outDATName
+	});
+
+	QString argv_inverse = Join (
+	{
+		parms,
+		cutDATName,
+		inDATName,
+		outDAT2Name
+	});
+
+	WriteColorGroup (inCol, inDATName);
+	WriteColorGroup (cutCol, cutDATName);
+
+	if (not RunExtProgram (Intersector, cfg::IntersectorPath, argv_normal))
+		return;
+
+	InsertOutput (outDATName, false, {inCol});
+
+	if (repeatInverse and RunExtProgram (Intersector, cfg::IntersectorPath, argv_inverse))
+		InsertOutput (outDAT2Name, false, {cutCol});
+
+	if (ui.cb_edges->isChecked() and CheckExtProgramPath (Isecalc) and
+		RunExtProgram (Isecalc, cfg::IsecalcPath, Join ({inDATName, cutDATName, edgesDATName})))
+	{
+		InsertOutput (edgesDATName, false, {});
+	}
+}
+
+// =============================================================================
+//
+void ExtProgramToolset::coverer()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Coverer))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::CovererUI ui;
+	ui.setupUi (dlg);
+	MakeColorComboBox (ui.cmb_col1);
+	MakeColorComboBox (ui.cmb_col2);
+
+	LDColor in1Col, in2Col;
+
+	forever
+	{
+		if (not dlg->exec())
+			return;
+
+		in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt();
+		in2Col = ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt();
+
+		if (in1Col == in2Col)
+		{
+			Critical ("Cannot use the same color group for both inputs!");
+			continue;
+		}
+
+		break;
+	}
+
+	QTemporaryFile in1dat, in2dat, outdat;
+	QString in1DATName, in2DATName, outDATName;
+
+	if (not MakeTempFile (in1dat, in1DATName) or
+		not MakeTempFile (in2dat, in2DATName) or
+		not MakeTempFile (outdat, outDATName))
+	{
+		return;
+	}
+
+	QString argv = Join (
+	{
+		(ui.cb_oldsweep->isChecked() ? "-s" : ""),
+		(ui.cb_reverse->isChecked() ? "-r" : ""),
+		(ui.dsb_segsplit->value() != 0 ? format ("-l %1", ui.dsb_segsplit->value()) : ""),
+		(ui.sb_bias->value() != 0 ? format ("-s %1", ui.sb_bias->value()) : ""),
+		in1DATName,
+		in2DATName,
+		outDATName
+	});
+
+	WriteColorGroup (in1Col, in1DATName);
+	WriteColorGroup (in2Col, in2DATName);
+
+	if (not RunExtProgram (Coverer, cfg::CovererPath, argv))
+		return;
+
+	InsertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+//
+void ExtProgramToolset::isecalc()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Isecalc))
+		return;
+
+	Ui::IsecalcUI ui;
+	QDialog* dlg = new QDialog;
+	ui.setupUi (dlg);
+
+	MakeColorComboBox (ui.cmb_col1);
+	MakeColorComboBox (ui.cmb_col2);
+
+	LDColor in1Col, in2Col;
+
+	// Run the dialog and validate input
+	forever
+	{
+		if (not dlg->exec())
+			return;
+
+		in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt();
+		in2Col = ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt();
+
+		if (in1Col == in2Col)
+		{
+			Critical ("Cannot use the same color group for both input and cutter!");
+			continue;
+		}
+
+		break;
+	}
+
+	QTemporaryFile in1dat, in2dat, outdat;
+	QString in1DATName, in2DATName, outDATName;
+
+	if (not MakeTempFile (in1dat, in1DATName) or
+		not MakeTempFile (in2dat, in2DATName) or
+		not MakeTempFile (outdat, outDATName))
+	{
+		return;
+	}
+
+	QString argv = Join (
+	{
+		in1DATName,
+		in2DATName,
+		outDATName
+	});
+
+	WriteColorGroup (in1Col, in1DATName);
+	WriteColorGroup (in2Col, in2DATName);
+	RunExtProgram (Isecalc, cfg::IsecalcPath, argv);
+	InsertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+//
+void ExtProgramToolset::edger2()
+{
+	setlocale (LC_ALL, "C");
+
+	if (not CheckExtProgramPath (Edger2))
+		return;
+
+	QDialog* dlg = new QDialog;
+	Ui::Edger2Dialog ui;
+	ui.setupUi (dlg);
+
+	if (not dlg->exec())
+		return;
+
+	QTemporaryFile in, out;
+	QString inName, outName;
+
+	if (not MakeTempFile (in, inName) or not MakeTempFile (out, outName))
+		return;
+
+	int unmatched = ui.unmatched->currentIndex();
+
+	QString argv = Join (
+	{
+		format ("-p %1", ui.precision->value()),
+		format ("-af %1", ui.flatAngle->value()),
+		format ("-ac %1", ui.condAngle->value()),
+		format ("-ae %1", ui.edgeAngle->value()),
+		ui.delLines->isChecked()     ? "-de" : "",
+		ui.delCondLines->isChecked() ? "-dc" : "",
+		ui.colored->isChecked()      ? "-c" : "",
+		ui.bfc->isChecked()          ? "-b" : "",
+		ui.convex->isChecked()       ? "-cx" : "",
+		ui.concave->isChecked()      ? "-cv" : "",
+		unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""),
+		inName,
+		outName,
+	});
+
+	WriteSelection (inName);
+
+	if (not RunExtProgram (Edger2, cfg::Edger2Path, argv))
+		return;
+
+	InsertOutput (outName, true, {});
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/extprogramtoolset.h	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,33 @@
+/*
+ *  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/>.
+ */
+
+#pragma once
+#include "toolset.h"
+
+class ExtProgramToolset : public Toolset
+{
+public:
+	ExtProgramToolset (MainWindow* parent);
+
+	Q_INVOKABLE void coverer();
+	Q_INVOKABLE void edger2();
+	Q_INVOKABLE void intersector();
+	Q_INVOKABLE void isecalc();
+	Q_INVOKABLE void rectifier();
+	Q_INVOKABLE void ytruder();
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/filetoolset.cpp	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,231 @@
+/*
+ *  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 <QFileDialog>
+#include <QMessageBox>
+#include "../configDialog.h"
+#include "../dialogs.h"
+#include "../glRenderer.h"
+#include "../ldDocument.h"
+#include "../mainwindow.h"
+#include "../partDownloader.h"
+#include "../primitives.h"
+#include "../dialogs/ldrawpathdialog.h"
+#include "../dialogs/newpartdialog.h"
+#include "filetoolset.h"
+#include "ui_makeprim.h"
+
+EXTERN_CFGENTRY (String, LDrawPath)
+
+FileToolset::FileToolset (MainWindow* parent) :
+	Toolset (parent) {}
+
+void FileToolset::newPart()
+{
+	NewPartDialog* dlg = new NewPartDialog (m_window);
+
+	if (dlg->exec() == QDialog::Accepted)
+	{
+		newFile();
+		dlg->fillHeader (CurrentDocument());
+		m_window->doFullRefresh();
+	}
+}
+
+void FileToolset::newFile()
+{
+	newFile();
+}
+
+void FileToolset::open()
+{
+	QString name = QFileDialog::getOpenFileName (m_window, "Open File", "", "LDraw files (*.dat *.ldr)");
+
+	if (name.isEmpty())
+		return;
+
+	OpenMainModel (name);
+}
+
+void FileToolset::save()
+{
+	m_window->save (CurrentDocument(), false);
+}
+
+void FileToolset::saveAs()
+{
+	m_window->save (CurrentDocument(), true);
+}
+
+void FileToolset::saveAll()
+{
+	for (LDDocument* file : LDDocument::explicitDocuments())
+		m_window->save (file, false);
+}
+
+void FileToolset::close()
+{
+	if (not CurrentDocument()->isSafeToClose())
+		return;
+
+	CurrentDocument()->dismiss();
+}
+
+void FileToolset::closeAll()
+{
+	if (not IsSafeToCloseAll())
+		return;
+
+	CloseAllDocuments();
+}
+
+void FileToolset::settings()
+{
+	(new ConfigDialog)->exec();
+}
+
+void FileToolset::setLDrawPath()
+{
+	(new LDrawPathDialog (cfg::LDrawPath, true))->exec();
+}
+
+void FileToolset::exit()
+{
+	Exit();
+}
+
+void FileToolset::insertFrom()
+{
+	QString fname = QFileDialog::getOpenFileName();
+	int idx = m_window->getInsertionPoint();
+
+	if (not fname.length())
+		return;
+
+	QFile f (fname);
+
+	if (not f.open (QIODevice::ReadOnly))
+	{
+		Critical (format ("Couldn't open %1 (%2)", fname, f.errorString()));
+		return;
+	}
+
+	LDObjectList objs = LoadFileContents (&f, null);
+
+	CurrentDocument()->clearSelection();
+
+	for (LDObject* obj : objs)
+	{
+		CurrentDocument()->insertObj (idx, obj);
+		obj->select();
+		m_window->R()->compileObject (obj);
+
+		idx++;
+	}
+
+	m_window->refresh();
+	m_window->scrollToSelection();
+}
+
+void FileToolset::exportTo()
+{
+	if (Selection().isEmpty())
+		return;
+
+	QString fname = QFileDialog::getSaveFileName();
+
+	if (fname.length() == 0)
+		return;
+
+	QFile file (fname);
+
+	if (not file.open (QIODevice::WriteOnly | QIODevice::Text))
+	{
+		Critical (format ("Unable to open %1 for writing (%2)", fname, file.errorString()));
+		return;
+	}
+
+	for (LDObject* obj : Selection())
+	{
+		QString contents = obj->asText();
+		QByteArray data = contents.toUtf8();
+		file.write (data, data.size());
+		file.write ("\r\n", 2);
+	}
+}
+
+void FileToolset::scanPrimitives()
+{
+	PrimitiveScanner::start();
+}
+
+void FileToolset::openSubfiles()
+{
+	for (LDObject* obj : Selection())
+	{
+		LDSubfile* ref = dynamic_cast<LDSubfile*> (obj);
+
+		if (ref == null or not ref->fileInfo()->isImplicit())
+			continue;
+
+		ref->fileInfo()->setImplicit (false);
+	}
+}
+
+void FileToolset::downloadFrom()
+{
+	PartDownloader::staticBegin();
+}
+
+void FileToolset::makePrimitive()
+{
+	PrimitivePrompt* dlg = new PrimitivePrompt (g_win);
+
+	if (not dlg->exec())
+		return;
+
+	int segs = dlg->ui->sb_segs->value();
+	int divs = dlg->ui->cb_hires->isChecked() ? HighResolution : LowResolution;
+	int num = dlg->ui->sb_ringnum->value();
+	PrimitiveType type =
+		dlg->ui->rb_circle->isChecked()   ? Circle :
+		dlg->ui->rb_cylinder->isChecked() ? Cylinder :
+		dlg->ui->rb_disc->isChecked()     ? Disc :
+		dlg->ui->rb_ndisc->isChecked()    ? DiscNeg :
+		dlg->ui->rb_ring->isChecked()     ? Ring : Cone;
+
+	LDDocument* f = GeneratePrimitive (type, segs, divs, num);
+	f->setImplicit (false);
+	m_window->save (f, false);
+}
+
+// These are not exactly file tools but I don't want to make another toolset just for 3 very small actions
+void FileToolset::help()
+{
+	// Not yet implemented
+}
+
+void FileToolset::about()
+{
+	AboutDialog().exec();
+}
+
+void FileToolset::aboutQt()
+{
+	QMessageBox::aboutQt (m_window);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/filetoolset.h	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,49 @@
+/*
+ *  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/>.
+ */
+
+#pragma once
+#include "toolset.h"
+
+class FileToolset : public Toolset
+{
+	Q_OBJECT
+
+public:
+	FileToolset (class MainWindow* parent);
+
+	Q_INVOKABLE void about();
+	Q_INVOKABLE void aboutQt();
+	Q_INVOKABLE void downloadFrom();
+	Q_INVOKABLE void close();
+	Q_INVOKABLE void closeAll();
+	Q_INVOKABLE void exit();
+	Q_INVOKABLE void exportTo();
+	Q_INVOKABLE void help();
+	Q_INVOKABLE void insertFrom();
+	Q_INVOKABLE void makePrimitive();
+	Q_INVOKABLE void newFile();
+	Q_INVOKABLE void newPart();
+	Q_INVOKABLE void open();
+	Q_INVOKABLE void openSubfiles();
+	Q_INVOKABLE void save();
+	Q_INVOKABLE void saveAll();
+	Q_INVOKABLE void saveAs();
+	Q_INVOKABLE void scanPrimitives();
+	Q_INVOKABLE void setLDrawPath();
+	Q_INVOKABLE void settings();
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/movetoolset.cpp	Sun Aug 30 17:20:55 2015 +0300
@@ -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 "../ldDocument.h"
+#include "../ldObjectMath.h"
+#include "../miscallenous.h"
+#include "../mainwindow.h"
+#include "movetoolset.h"
+
+MoveToolset::MoveToolset (MainWindow* parent) :
+	Toolset (parent)
+{
+}
+
+void MoveToolset::moveSelection (bool up)
+{
+	LDObjectList objs = Selection();
+	LDObject::moveObjects (objs, up);
+	m_window->buildObjList();
+}
+
+void MoveToolset::moveUp()
+{
+	moveSelection (true);
+}
+
+void MoveToolset::moveDown()
+{
+	moveSelection (false);
+}
+
+void MoveToolset::gridCoarse()
+{
+	cfg::Grid = Grid::Coarse;
+	m_window->updateGridToolBar();
+}
+
+void MoveToolset::gridMedium()
+{
+	cfg::Grid = Grid::Medium;
+	m_window->updateGridToolBar();
+}
+
+void MoveToolset::gridFine()
+{
+	cfg::Grid = Grid::Fine;
+	m_window->updateGridToolBar();
+}
+
+void MoveToolset::moveObjects (Vertex vect)
+{
+	// Apply the grid values
+	vect *= *CurrentGrid().coordinateSnap;
+
+	for (LDObject* obj : Selection())
+		obj->move (vect);
+}
+
+void MoveToolset::moveXNeg()
+{
+	moveObjects ({-1, 0, 0});
+}
+
+void MoveToolset::moveYNeg()
+{
+	moveObjects ({0, -1, 0});
+}
+
+void MoveToolset::moveZNeg()
+{
+	moveObjects ({0, 0, -1});
+}
+
+void MoveToolset::moveXPos()
+{
+	moveObjects ({1, 0, 0});
+}
+
+void MoveToolset::moveYPos()
+{
+	moveObjects ({0, 1, 0});
+}
+
+void MoveToolset::moveZPos()
+{
+	moveObjects ({0, 0, 1});
+}
+
+static double GetRotateActionAngle()
+{
+	return (Pi * *CurrentGrid().angleSnap) / 180;
+}
+
+void MoveToolset::rotateXPos()
+{
+	RotateObjects (1, 0, 0, GetRotateActionAngle(), Selection());
+}
+void MoveToolset::rotateYPos()
+{
+	RotateObjects (0, 1, 0, GetRotateActionAngle(), Selection());
+}
+void MoveToolset::rotateZPos()
+{
+	RotateObjects (0, 0, 1, GetRotateActionAngle(), Selection());
+}
+void MoveToolset::rotateXNeg()
+{
+	RotateObjects (-1, 0, 0, GetRotateActionAngle(), Selection());
+}
+void MoveToolset::rotateYNeg()
+{
+	RotateObjects (0, -1, 0, GetRotateActionAngle(), Selection());
+}
+void MoveToolset::rotateZNeg()
+{
+	RotateObjects (0, 0, -1, GetRotateActionAngle(), Selection());
+}
+
+void MoveToolset::configureRotationPoint()
+{
+	ConfigureRotationPoint();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/movetoolset.h	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,51 @@
+/*
+ *  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/>.
+ */
+
+#pragma once
+#include "toolset.h"
+
+class MoveToolset : public Toolset
+{
+	Q_OBJECT
+
+public:
+	explicit MoveToolset (MainWindow* parent);
+
+	Q_INVOKABLE void configureRotationPoint();
+	Q_INVOKABLE void gridCoarse();
+	Q_INVOKABLE void gridFine();
+	Q_INVOKABLE void gridMedium();
+	Q_INVOKABLE void moveDown();
+	Q_INVOKABLE void moveUp();
+	Q_INVOKABLE void moveXNeg();
+	Q_INVOKABLE void moveXPos();
+	Q_INVOKABLE void moveYNeg();
+	Q_INVOKABLE void moveYPos();
+	Q_INVOKABLE void moveZNeg();
+	Q_INVOKABLE void moveZPos();
+	Q_INVOKABLE void rotateXNeg();
+	Q_INVOKABLE void rotateXPos();
+	Q_INVOKABLE void rotateYNeg();
+	Q_INVOKABLE void rotateYPos();
+	Q_INVOKABLE void rotateZNeg();
+	Q_INVOKABLE void rotateZPos();
+
+private:
+	void moveSelection (bool up);
+	void moveObjects (Vertex vect);
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/toolset.cpp	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,42 @@
+/*
+ *  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 "../mainwindow.h"
+#include "algorithmtoolset.h"
+#include "basictoolset.h"
+#include "extprogramtoolset.h"
+#include "filetoolset.h"
+#include "movetoolset.h"
+#include "toolset.h"
+#include "viewtoolset.h"
+
+Toolset::Toolset (MainWindow* parent) :
+	QObject (parent),
+	m_window (parent) {}
+
+QVector<Toolset*> Toolset::createToolsets (MainWindow* parent)
+{
+	QVector<Toolset*> tools;
+	tools << new AlgorithmToolset (parent);
+	tools << new BasicToolset (parent);
+	tools << new ExtProgramToolset (parent);
+	tools << new FileToolset (parent);
+	tools << new MoveToolset (parent);
+	tools << new ViewToolset (parent);
+	return tools;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/toolset.h	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,35 @@
+/*
+ *  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/>.
+ */
+
+#pragma once
+#include <QObject>
+#include "../main.h"
+
+class MainWindow;
+
+class Toolset : public QObject
+{
+	Q_OBJECT
+public:
+	Toolset (MainWindow* parent);
+
+	static QVector<Toolset*> createToolsets (MainWindow* parent);
+
+protected:
+	MainWindow* m_window;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/viewtoolset.cpp	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,310 @@
+/*
+ *  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 <QFileDialog>
+#include <QInputDialog>
+#include "../mainwindow.h"
+#include "../ldDocument.h"
+#include "../miscallenous.h"
+#include "../glRenderer.h"
+#include "../primitives.h"
+#include "../colors.h"
+#include "../dialogs.h"
+#include "../glCompiler.h"
+#include "viewtoolset.h"
+
+EXTERN_CFGENTRY (Bool, DrawWireframe)
+EXTERN_CFGENTRY (Bool, BFCRedGreenView)
+EXTERN_CFGENTRY (Bool, DrawAngles)
+EXTERN_CFGENTRY (Bool, RandomColors)
+EXTERN_CFGENTRY (Bool, DrawSurfaces)
+EXTERN_CFGENTRY (Bool, DrawEdgeLines)
+EXTERN_CFGENTRY (Bool, DrawConditionalLines)
+EXTERN_CFGENTRY (Bool, DrawAxes)
+
+ViewToolset::ViewToolset (MainWindow *parent) :
+	Toolset (parent) {}
+
+void ViewToolset::selectAll()
+{
+	for (LDObject* obj : CurrentDocument()->objects())
+		obj->select();
+}
+
+void ViewToolset::selectByColor()
+{
+	if (Selection().isEmpty())
+		return;
+
+	QList<LDColor> colors;
+
+	for (LDObject* obj : Selection())
+	{
+		if (obj->isColored())
+			colors << obj->color();
+	}
+
+	removeDuplicates (colors);
+	CurrentDocument()->clearSelection();
+
+	for (LDObject* obj : CurrentDocument()->objects())
+	{
+		if (colors.contains (obj->color()))
+			obj->select();
+	}
+}
+
+void ViewToolset::selectByType()
+{
+	if (Selection().isEmpty())
+		return;
+
+	QList<LDObjectType> types;
+	QStringList subfilenames;
+
+	for (LDObject* obj : Selection())
+	{
+		types << obj->type();
+
+		if (types.last() == OBJ_Subfile)
+			subfilenames << static_cast<LDSubfile*> (obj)->fileInfo()->name();
+	}
+
+	removeDuplicates (types);
+	removeDuplicates (subfilenames);
+	CurrentDocument()->clearSelection();
+
+	for (LDObject* obj : CurrentDocument()->objects())
+	{
+		LDObjectType type = obj->type();
+
+		if (not types.contains (type))
+			continue;
+
+		// For subfiles, type check is not enough, we check the name of the document as well.
+		if (type == OBJ_Subfile and not subfilenames.contains (static_cast<LDSubfile*> (obj)->fileInfo()->name()))
+			continue;
+
+		obj->select();
+	}
+}
+
+void ViewToolset::resetView()
+{
+	m_window->R()->resetAngles();
+	m_window->R()->update();
+}
+
+void ViewToolset::screenshot()
+{
+	setlocale (LC_ALL, "C");
+
+	int w, h;
+	uchar* imgdata = m_window->R()->getScreencap (w, h);
+	QImage img = GetImageFromScreencap (imgdata, w, h);
+
+	QString root = Basename (CurrentDocument()->name());
+
+	if (root.right (4) == ".dat")
+		root.chop (4);
+
+	QString defaultname = (root.length() > 0) ? format ("%1.png", root) : "";
+	QString fname = QFileDialog::getSaveFileName (m_window, "Save Screencap", defaultname,
+				"PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)");
+
+	if (not fname.isEmpty() and not img.save (fname))
+		Critical (format ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno)));
+
+	delete[] imgdata;
+}
+
+void ViewToolset::axes()
+{
+	cfg::DrawAxes = not cfg::DrawAxes;
+	m_window->updateActions();
+	m_window->R()->update();
+}
+
+void ViewToolset::visibilityToggle()
+{
+	for (LDObject* obj : Selection())
+		obj->setHidden (not obj->isHidden());
+}
+
+void ViewToolset::visibilityHide()
+{
+	for (LDObject* obj : Selection())
+		obj->setHidden (true);
+}
+
+void ViewToolset::visibilityReveal()
+{
+	for (LDObject* obj : Selection())
+		obj->setHidden (false);
+}
+
+void ViewToolset::wireframe()
+{
+	cfg::DrawWireframe = not cfg::DrawWireframe;
+	m_window->R()->refresh();
+}
+
+void ViewToolset::setOverlay()
+{
+	OverlayDialog dlg;
+
+	if (not dlg.exec())
+		return;
+
+	m_window->R()->setupOverlay ((ECamera) dlg.camera(), dlg.fpath(), dlg.ofsx(),
+		dlg.ofsy(), dlg.lwidth(), dlg.lheight());
+}
+
+void ViewToolset::clearOverlay()
+{
+	m_window->R()->clearOverlay();
+}
+
+void ViewToolset::drawAngles()
+{
+	cfg::DrawAngles = not cfg::DrawAngles;
+	m_window->R()->refresh();
+}
+
+void ViewToolset::setDrawDepth()
+{
+	if (m_window->R()->camera() == EFreeCamera)
+		return;
+
+	bool ok;
+	double depth = QInputDialog::getDouble (m_window, "Set Draw Depth",
+		format ("Depth value for %1 Camera:", m_window->R()->getCameraName()),
+		m_window->R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok);
+
+	if (ok)
+		m_window->R()->setDepthValue (depth);
+}
+
+#if 0
+// This is a test to draw a dummy axle. Meant to be used as a primitive gallery,
+// but I can't figure how to generate these pictures properly. Multi-threading
+// these is an immense pain.
+void ViewToolset::testpic()
+{
+	LDDocument* file = getFile ("axle.dat");
+	setlocale (LC_ALL, "C");
+
+	if (not file)
+	{
+		critical ("couldn't load axle.dat");
+		return;
+	}
+
+	int w, h;
+
+	GLRenderer* rend = new GLRenderer;
+	rend->resize (64, 64);
+	rend->setAttribute (Qt::WA_DontShowOnScreen);
+	rend->show();
+	rend->setFile (file);
+	rend->setDrawOnly (true);
+	rend->compileAllObjects();
+	rend->initGLData();
+	rend->drawGLScene();
+
+	uchar* imgdata = rend->screencap (w, h);
+	QImage img = imageFromScreencap (imgdata, w, h);
+
+	if (img.isNull())
+	{
+		critical ("Failed to create the image!\n");
+	}
+	else
+	{
+		QLabel* label = new QLabel;
+		QDialog* dlg = new QDialog;
+		label->setPixmap (QPixmap::fromImage (img));
+		QVBoxLayout* layout = new QVBoxLayout (dlg);
+		layout->addWidget (label);
+		dlg->exec();
+	}
+
+	delete[] imgdata;
+	rend->deleteLater();
+}
+#endif
+
+void ViewToolset::bfcView()
+{
+	cfg::BFCRedGreenView = not cfg::BFCRedGreenView;
+
+	if (cfg::BFCRedGreenView)
+		cfg::RandomColors = false;
+
+	m_window->updateActions();
+	m_window->R()->refresh();
+}
+
+void ViewToolset::jumpTo()
+{
+	bool ok;
+	int defval = 0;
+	LDObject* obj;
+
+	if (Selection().size() == 1)
+		defval = Selection()[0]->lineNumber();
+
+	int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval,
+		1, CurrentDocument()->getObjectCount(), 1, &ok);
+
+	if (not ok or (obj = CurrentDocument()->getObject (idx - 1)) == null)
+		return;
+
+	CurrentDocument()->clearSelection();
+	obj->select();
+	m_window->updateSelection();
+}
+
+void ViewToolset::randomColors()
+{
+	cfg::RandomColors = not cfg::RandomColors;
+
+	if (cfg::RandomColors)
+		cfg::BFCRedGreenView = false;
+
+	m_window->updateActions();
+	m_window->R()->refresh();
+}
+
+void ViewToolset::drawSurfaces()
+{
+	cfg::DrawSurfaces = not cfg::DrawSurfaces;
+	m_window->updateActions();
+}
+
+void ViewToolset::drawEdgeLines()
+{
+	cfg::DrawEdgeLines = not cfg::DrawEdgeLines;
+	m_window->updateActions();
+}
+
+void ViewToolset::drawConditionalLines()
+{
+	cfg::DrawConditionalLines = not cfg::DrawConditionalLines;
+	m_window->updateActions();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/toolsets/viewtoolset.h	Sun Aug 30 17:20:55 2015 +0300
@@ -0,0 +1,49 @@
+/*
+ *  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/>.
+ */
+
+#pragma once
+#include "toolset.h"
+
+class ViewToolset : public Toolset
+{
+	Q_OBJECT
+
+public:
+	ViewToolset (MainWindow* parent);
+
+	Q_INVOKABLE void axes();
+	Q_INVOKABLE void bfcView();
+	Q_INVOKABLE void clearOverlay();
+	Q_INVOKABLE void drawAngles();
+	Q_INVOKABLE void drawConditionalLines();
+	Q_INVOKABLE void drawEdgeLines();
+	Q_INVOKABLE void drawSurfaces();
+	Q_INVOKABLE void jumpTo();
+	Q_INVOKABLE void randomColors();
+	Q_INVOKABLE void resetView();
+	Q_INVOKABLE void screenshot();
+	Q_INVOKABLE void selectAll();
+	Q_INVOKABLE void selectByColor();
+	Q_INVOKABLE void selectByType();
+	Q_INVOKABLE void setDrawDepth();
+	Q_INVOKABLE void setOverlay();
+	Q_INVOKABLE void visibilityHide();
+	Q_INVOKABLE void visibilityReveal();
+	Q_INVOKABLE void visibilityToggle();
+	Q_INVOKABLE void wireframe();
+};

mercurial