src/actionsEdit.cc

changeset 655
b376645315ab
child 662
2f1bd9112408
child 706
d79083b9f74d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/actionsEdit.cc	Sat Mar 29 05:26:10 2014 +0200
@@ -0,0 +1,836 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QSpinBox>
+#include <QCheckBox>
+#include <QBoxLayout>
+#include <QClipboard>
+#include "mainWindow.h"
+#include "main.h"
+#include "ldDocument.h"
+#include "colorSelector.h"
+#include "miscallenous.h"
+#include "radioGroup.h"
+#include "glRenderer.h"
+#include "dialogs.h"
+#include "colors.h"
+#include "ui_replcoords.h"
+#include "ui_editraw.h"
+#include "ui_flip.h"
+#include "ui_addhistoryline.h"
+
+cfg (Bool, edit_schemanticinline, false);
+extern_cfg (String, ld_defaultuser);
+
+// =============================================================================
+//
+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 (data.length() > 0)
+			data += "\n";
+
+		data += obj->asText();
+		++num;
+	}
+
+	qApp->clipboard()->setText (data);
+	return num;
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Cut, CTRL (X))
+{
+	int num = copyToClipboard();
+	deleteSelection();
+	print (tr ("%1 objects cut"), num);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Copy, CTRL (C))
+{
+	int num = copyToClipboard();
+	print (tr ("%1 objects copied"), num);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Paste, CTRL (V))
+{
+	const QString clipboardText = qApp->clipboard()->text();
+	int idx = getInsertionPoint();
+	getCurrentDocument()->clearSelection();
+	int num = 0;
+
+	for (QString line : clipboardText.split ("\n"))
+	{
+		LDObject* pasted = parseLine (line);
+		getCurrentDocument()->insertObj (idx++, pasted);
+		pasted->select();
+		R()->compileObject (pasted);
+		++num;
+	}
+
+	print (tr ("%1 objects pasted"), num);
+	refresh();
+	scrollToSelection();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Delete, KEY (Delete))
+{
+	int num = deleteSelection();
+	print (tr ("%1 objects deleted"), num);
+}
+
+// =============================================================================
+//
+static void doInline (bool deep)
+{
+	LDObjectList sel = selection();
+
+	for (LDObject* obj : sel)
+	{
+		// Get the index of the subfile so we know where to insert the
+		// inlined contents.
+		long idx = obj->lineNumber();
+
+		if (idx == -1)
+			continue;
+
+		LDObjectList objs;
+
+		if (obj->type() == LDObject::ESubfile)
+		{
+			LDSubfile::InlineFlags flags = deep ? LDSubfile::DeepCacheInline : LDSubfile::CacheInline;
+			objs = static_cast<LDSubfile*> (obj)->inlineContents (flags);
+		}
+		else
+			continue;
+
+		// Merge in the inlined objects
+		for (LDObject* inlineobj : objs)
+		{
+			QString line = inlineobj->asText();
+			inlineobj->destroy();
+			LDObject* newobj = parseLine (line);
+			getCurrentDocument()->insertObj (idx++, newobj);
+			newobj->select();
+			g_win->R()->compileObject (newobj);
+		}
+
+		// Delete the subfile now as it's been inlined.
+		obj->destroy();
+	}
+
+	g_win->refresh();
+}
+
+DEFINE_ACTION (Inline, CTRL (I))
+{
+	doInline (false);
+}
+
+DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I))
+{
+	doInline (true);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SplitQuads, 0)
+{
+	LDObjectList objs = selection();
+	int num = 0;
+
+	for (LDObject* obj : objs)
+	{
+		if (obj->type() != LDObject::EQuad)
+			continue;
+
+		// Find the index of this quad
+		long index = obj->lineNumber();
+
+		if (index == -1)
+			return;
+
+		QList<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles();
+
+		// Replace the quad with the first triangle and add the second triangle
+		// after the first one.
+		getCurrentDocument()->setObject (index, triangles[0]);
+		getCurrentDocument()->insertObj (index + 1, triangles[1]);
+
+		for (LDTriangle* t : triangles)
+			R()->compileObject (t);
+
+		// Delete this quad now, it has been split.
+		obj->destroy();
+
+		num++;
+	}
+
+	print ("%1 quadrilaterals split", num);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (EditRaw, KEY (F9))
+{
+	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() == LDObject::EError)
+		ui.errorDescription->setText (static_cast<LDError*> (obj)->reason());
+	else
+	{
+		ui.errorDescription->hide();
+		ui.errorIcon->hide();
+	}
+
+	if (!dlg->exec())
+		return;
+
+	LDObject* oldobj = obj;
+
+	// Reinterpret it from the text of the input field
+	obj = parseLine (ui.code->text());
+	oldobj->replace (obj);
+
+	// Refresh
+	R()->compileObject (obj);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (SetColor, KEY (C))
+{
+	if (selection().isEmpty())
+		return;
+
+	int colnum;
+	int defcol = -1;
+
+	LDObjectList objs = selection();
+
+	// If all selected objects have the same color, said color is our default
+	// value to the color selection dialog.
+	defcol = getSelectedColor();
+
+	// Show the dialog to the user now and ask for a color.
+	if (ColorSelector::selectColor (colnum, defcol, g_win))
+	{
+		for (LDObject* obj : objs)
+		{
+			if (obj->isColored() == false)
+				continue;
+
+			obj->setColor (colnum);
+			R()->compileObject (obj);
+		}
+
+		refresh();
+	}
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Borders, CTRL_SHIFT (B))
+{
+	LDObjectList objs = selection();
+	int num = 0;
+
+	for (LDObject* obj : objs)
+	{
+		const LDObject::Type type = obj->type();
+		if (type != LDObject::EQuad && type != LDObject::ETriangle)
+			continue;
+
+		int numLines;
+		LDLine* lines[4];
+
+		if (type == LDObject::EQuad)
+		{
+			numLines = 4;
+
+			LDQuad* quad = static_cast<LDQuad*> (obj);
+			lines[0] = new LDLine (quad->vertex (0), quad->vertex (1));
+			lines[1] = new LDLine (quad->vertex (1), quad->vertex (2));
+			lines[2] = new LDLine (quad->vertex (2), quad->vertex (3));
+			lines[3] = new LDLine (quad->vertex (3), quad->vertex (0));
+		}
+		else
+		{
+			numLines = 3;
+
+			LDTriangle* tri = static_cast<LDTriangle*> (obj);
+			lines[0] = new LDLine (tri->vertex (0), tri->vertex (1));
+			lines[1] = new LDLine (tri->vertex (1), tri->vertex (2));
+			lines[2] = new LDLine (tri->vertex (2), tri->vertex (0));
+		}
+
+		for (int i = 0; i < numLines; ++i)
+		{
+			long idx = obj->lineNumber() + i + 1;
+
+			lines[i]->setColor (edgecolor);
+			getCurrentDocument()->insertObj (idx, lines[i]);
+			R()->compileObject (lines[i]);
+		}
+
+		num += numLines;
+	}
+
+	print (tr ("Added %1 border lines"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (CornerVerts, 0)
+{
+	int num = 0;
+
+	for (LDObject* obj : selection())
+	{
+		if (obj->vertices() < 2)
+			continue;
+
+		int ln = obj->lineNumber();
+
+		for (int i = 0; i < obj->vertices(); ++i)
+		{
+			LDVertex* vert = new LDVertex;
+			vert->pos = obj->vertex (i);
+			vert->setColor (obj->color());
+
+			getCurrentDocument()->insertObj (++ln, vert);
+			R()->compileObject (vert);
+			++num;
+		}
+	}
+
+	print (tr ("Added %1 vertices"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+static void doMoveSelection (const bool up)
+{
+	LDObjectList objs = selection();
+	LDObject::moveObjects (objs, up);
+	g_win->buildObjList();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (MoveUp, KEY (PageUp))
+{
+	doMoveSelection (true);
+}
+
+DEFINE_ACTION (MoveDown, KEY (PageDown))
+{
+	doMoveSelection (false);
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Undo, CTRL (Z))
+{
+	getCurrentDocument()->undo();
+}
+
+DEFINE_ACTION (Redo, CTRL_SHIFT (Z))
+{
+	getCurrentDocument()->redo();
+}
+
+// =============================================================================
+//
+void doMoveObjects (Vertex vect)
+{
+	// Apply the grid values
+	vect[X] *= *currentGrid().confs[Grid::X];
+	vect[Y] *= *currentGrid().confs[Grid::Y];
+	vect[Z] *= *currentGrid().confs[Grid::Z];
+
+	for (LDObject* obj : selection())
+	{
+		obj->move (vect);
+		g_win->R()->compileObject (obj);
+	}
+
+	g_win->refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (MoveXNeg, KEY (Left))
+{
+	doMoveObjects ({ -1, 0, 0});
+}
+
+DEFINE_ACTION (MoveYNeg, KEY (Home))
+{
+	doMoveObjects ({0, -1, 0});
+}
+
+DEFINE_ACTION (MoveZNeg, KEY (Down))
+{
+	doMoveObjects ({0, 0, -1});
+}
+
+DEFINE_ACTION (MoveXPos, KEY (Right))
+{
+	doMoveObjects ({1, 0, 0});
+}
+
+DEFINE_ACTION (MoveYPos, KEY (End))
+{
+	doMoveObjects ({0, 1, 0});
+}
+
+DEFINE_ACTION (MoveZPos, KEY (Up))
+{
+	doMoveObjects ({0, 0, 1});
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Invert, CTRL_SHIFT (W))
+{
+	LDObjectList sel = selection();
+
+	for (LDObject* obj : sel)
+	{
+		obj->invert();
+		R()->compileObject (obj);
+	}
+
+	refresh();
+}
+
+// =============================================================================
+//
+static void rotateVertex (Vertex& v, const Vertex& rotpoint, const Matrix& transform)
+{
+	v.move (-rotpoint);
+	v.transform (transform, g_origin);
+	v.move (rotpoint);
+}
+
+// =============================================================================
+//
+static void doRotate (const int l, const int m, const int n)
+{
+	LDObjectList sel = selection();
+	QList<Vertex*> queue;
+	const Vertex rotpoint = rotPoint (sel);
+	const double angle = (pi * *currentGrid().confs[Grid::Angle]) / 180,
+				 cosangle = cos (angle),
+				 sinangle = sin (angle);
+
+	// ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2
+	Matrix transform (
+	{
+		(l* l * (1 - cosangle)) + cosangle,
+		(m* l * (1 - cosangle)) - (n* sinangle),
+		(n* l * (1 - cosangle)) + (m* sinangle),
+
+		(l* m * (1 - cosangle)) + (n* sinangle),
+		(m* m * (1 - cosangle)) + cosangle,
+		(n* m * (1 - cosangle)) - (l* sinangle),
+
+		(l* n * (1 - cosangle)) - (m* sinangle),
+		(m* n * (1 - cosangle)) + (l* sinangle),
+		(n* n * (1 - cosangle)) + cosangle
+	});
+
+	// Apply the above matrix to everything
+	for (LDObject* obj : sel)
+	{
+		if (obj->vertices())
+		{
+			for (int i = 0; i < obj->vertices(); ++i)
+			{
+				Vertex v = obj->vertex (i);
+				rotateVertex (v, rotpoint, transform);
+				obj->setVertex (i, v);
+			}
+		}
+		elif (obj->hasMatrix())
+		{
+			LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
+
+			// Transform the position
+			Vertex v = mo->position();
+			rotateVertex (v, rotpoint, transform);
+			mo->setPosition (v);
+
+			// Transform the matrix
+			mo->setTransform (transform * mo->transform());
+		}
+		elif (obj->type() == LDObject::EVertex)
+		{
+			LDVertex* vert = static_cast<LDVertex*> (obj);
+			Vertex v = vert->pos;
+			rotateVertex (v, rotpoint, transform);
+			vert->pos = v;
+		}
+
+		g_win->R()->compileObject (obj);
+	}
+
+	g_win->refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (RotateXPos, CTRL (Right))
+{
+	doRotate (1, 0, 0);
+}
+DEFINE_ACTION (RotateYPos, CTRL (End))
+{
+	doRotate (0, 1, 0);
+}
+DEFINE_ACTION (RotateZPos, CTRL (Up))
+{
+	doRotate (0, 0, 1);
+}
+DEFINE_ACTION (RotateXNeg, CTRL (Left))
+{
+	doRotate (-1, 0, 0);
+}
+DEFINE_ACTION (RotateYNeg, CTRL (Home))
+{
+	doRotate (0, -1, 0);
+}
+DEFINE_ACTION (RotateZNeg, CTRL (Down))
+{
+	doRotate (0, 0, -1);
+}
+
+DEFINE_ACTION (RotationPoint, (0))
+{
+	configRotationPoint();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (RoundCoordinates, 0)
+{
+	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();
+
+			for_axes (ax)
+				roundToDecimals (v[ax], 3);
+
+			// Let matrix values be rounded to 4 decimals,
+			// they need that extra precision
+			for (int i = 0; i < 9; ++i)
+				roundToDecimals (t[i], 4);
+
+			mo->setPosition (v);
+			mo->setTransform (t);
+			num += 10;
+		}
+		else
+		{
+			for (int i = 0; i < obj->vertices(); ++i)
+			{
+				Vertex v = obj->vertex (i);
+
+				for_axes (ax)
+					roundToDecimals (v[ax], 3);
+
+				obj->setVertex (i, v);
+				R()->compileObject (obj);
+				num += 3;
+			}
+		}
+	}
+
+	print (tr ("Rounded %1 values"), num);
+	refreshObjectList();
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Uncolorize, 0)
+{
+	int num = 0;
+
+	for (LDObject* obj : selection())
+	{
+		if (obj->isColored() == false)
+			continue;
+
+		int col = maincolor;
+
+		if (obj->type() == LDObject::ELine || obj->type() == LDObject::ECondLine)
+			col = edgecolor;
+
+		obj->setColor (col);
+		R()->compileObject (obj);
+		num++;
+	}
+
+	print (tr ("%1 objects uncolored"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (ReplaceCoords, CTRL (R))
+{
+	QDialog* dlg = new QDialog (g_win);
+	Ui::ReplaceCoordsUI ui;
+	ui.setupUi (dlg);
+
+	if (!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->vertices(); ++i)
+		{
+			Vertex v = obj->vertex (i);
+
+			for (Axis ax : sel)
+			{
+				double& coord = v[ax];
+
+				if (any || coord == search)
+				{
+					if (!rel)
+						coord = 0;
+
+					coord += replacement;
+					num++;
+				}
+			}
+
+			obj->setVertex (i, v);
+			R()->compileObject (obj);
+		}
+	}
+
+	print (tr ("Altered %1 values"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Flip, CTRL_SHIFT (F))
+{
+	QDialog* dlg = new QDialog;
+	Ui::FlipUI ui;
+	ui.setupUi (dlg);
+
+	if (!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->vertices(); ++i)
+		{
+			Vertex v = obj->vertex (i);
+
+			for (Axis ax : sel)
+				v[ax] *= -1;
+
+			obj->setVertex (i, v);
+			R()->compileObject (obj);
+		}
+	}
+
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Demote, 0)
+{
+	LDObjectList sel = selection();
+	int num = 0;
+
+	for (LDObject* obj : sel)
+	{
+		if (obj->type() != LDObject::ECondLine)
+			continue;
+
+		LDLine* repl = static_cast<LDCondLine*> (obj)->demote();
+		R()->compileObject (repl);
+		++num;
+	}
+
+	print (tr ("Demoted %1 conditional lines"), num);
+	refresh();
+}
+
+// =============================================================================
+//
+static bool isColorUsed (int colnum)
+{
+	for (LDObject* obj : getCurrentDocument()->objects())
+		if (obj->isColored() && obj->color() == colnum)
+			return true;
+
+	return false;
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (Autocolor, 0)
+{
+	int colnum = 0;
+
+	while (colnum < MAX_COLORS && (getColor (colnum) == null || isColorUsed (colnum)))
+		colnum++;
+
+	if (colnum >= MAX_COLORS)
+	{
+		print (tr ("Cannot auto-color: all colors are in use!"));
+		return;
+	}
+
+	for (LDObject* obj : selection())
+	{
+		if (obj->isColored() == false)
+			continue;
+
+		obj->setColor (colnum);
+		R()->compileObject (obj);
+	}
+
+	print (tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name);
+	refresh();
+}
+
+// =============================================================================
+//
+DEFINE_ACTION (AddHistoryLine, 0)
+{
+	LDObject* obj;
+	bool ishistory = false,
+		 prevIsHistory = false;
+
+	QDialog* dlg = new QDialog;
+	Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
+	ui->setupUi (dlg);
+	ui->m_username->setText (ld_defaultuser);
+	ui->m_date->setDate (QDate::currentDate());
+	ui->m_comment->setFocus();
+
+	if (!dlg->exec())
+		return;
+
+	// Create the comment object based on input
+	QString commentText = format ("!HISTORY %1 [%2] %3",
+		ui->m_date->date().toString ("yyyy-MM-dd"),
+		ui->m_username->text(),
+		ui->m_comment->text());
+
+	LDComment* comm = new LDComment (commentText);
+
+	// Find a spot to place the new comment
+	for (
+		obj = getCurrentDocument()->getObject (0);
+		obj && obj->next() && !obj->next()->isScemantic();
+		obj = obj->next()
+	)
+	{
+		LDComment* comm = dynamic_cast<LDComment*> (obj);
+
+		if (comm != null && comm->text().startsWith ("!HISTORY "))
+			ishistory = true;
+
+		if (prevIsHistory && !ishistory)
+		{
+			// Last line was history, this isn't, thus insert the new history
+			// line here.
+			break;
+		}
+
+		prevIsHistory = ishistory;
+	}
+
+	int idx = obj ? obj->lineNumber() : 0;
+	getCurrentDocument()->insertObj (idx++, comm);
+
+	// If we're adding a history line right before a scemantic object, pad it
+	// an empty line
+	if (obj && obj->next() && obj->next()->isScemantic())
+		getCurrentDocument()->insertObj (idx, new LDEmpty);
+
+	buildObjList();
+	delete ui;
+}

mercurial