src/actions/EditActions.cc

Sun, 23 Feb 2014 18:49:24 +0200

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Sun, 23 Feb 2014 18:49:24 +0200
changeset 684
8a56b1d4d44d
parent 681
c1cc036c6e1f
child 690
9e9c52ca955e
permissions
-rw-r--r--

- greatly improved the GL compiler, now deals colors and object removal properly

/*
 *  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 "../Document.h"
#include "../ColorSelector.h"
#include "../Misc.h"
#include "../Widgets.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->raw();
		++num;
	}

	qApp->clipboard()->setText (data);
	return num;
}

// =============================================================================
// -----------------------------------------------------------------------------
DEFINE_ACTION (Cut, CTRL (X))
{
	int num = copyToClipboard();
	deleteSelection();
	log (tr ("%1 objects cut"), num);
}

// =============================================================================
// -----------------------------------------------------------------------------
DEFINE_ACTION (Copy, CTRL (C))
{
	int num = copyToClipboard();
	log (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;
	}

	log (tr ("%1 objects pasted"), num);
	refresh();
	scrollToSelection();
}

// =============================================================================
// -----------------------------------------------------------------------------
DEFINE_ACTION (Delete, KEY (Delete))
{
	int num = deleteSelection();
	log (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->getIndex();

		if (idx == -1 || obj->getType() != LDObject::ESubfile)
			continue;

		LDObjectList objs = static_cast<LDSubfile*> (obj)->inlineContents (deep, false);

		// Merge in the inlined objects
		for (LDObject * inlineobj : objs)
		{
			QString line = inlineobj->raw();
			inlineobj->deleteSelf();
			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->deleteSelf();
	}

	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->getType() != LDObject::EQuad)
			continue;

		// Find the index of this quad
		long index = obj->getIndex();

		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->deleteSelf();

		num++;
	}

	log ("%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->raw());

	if (obj->getType() == 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->getType();
		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->getVertex (0), quad->getVertex (1));
			lines[1] = new LDLine (quad->getVertex (1), quad->getVertex (2));
			lines[2] = new LDLine (quad->getVertex (2), quad->getVertex (3));
			lines[3] = new LDLine (quad->getVertex (3), quad->getVertex (0));
		}
		else
		{
			numLines = 3;

			LDTriangle* tri = static_cast<LDTriangle*> (obj);
			lines[0] = new LDLine (tri->getVertex (0), tri->getVertex (1));
			lines[1] = new LDLine (tri->getVertex (1), tri->getVertex (2));
			lines[2] = new LDLine (tri->getVertex (2), tri->getVertex (0));
		}

		for (int i = 0; i < numLines; ++i)
		{
			long idx = obj->getIndex() + i + 1;

			lines[i]->setColor (edgecolor);
			getCurrentDocument()->insertObj (idx, lines[i]);
			R()->compileObject (lines[i]);
		}

		num += numLines;
	}

	log (tr ("Added %1 border lines"), num);
	refresh();
}

// =============================================================================
// -----------------------------------------------------------------------------
DEFINE_ACTION (CornerVerts, 0)
{
	int num = 0;

	for (LDObject* obj : selection())
	{
		if (obj->vertices() < 2)
			continue;

		int idx = obj->getIndex();

		for (int i = 0; i < obj->vertices(); ++i)
		{
			LDVertex* vert = new LDVertex;
			vert->pos = obj->getVertex (i);
			vert->setColor (obj->getColor());

			getCurrentDocument()->insertObj (++idx, vert);
			R()->compileObject (vert);
			++num;
		}
	}

	log (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];

	QTime t0 = QTime::currentTime();
	for (LDObject* obj : selection())
	{
		obj->move (vect);
		g_win->R()->compileObject (obj);
	}
	fprint (stderr, "Move: %1ms\n", t0.msecsTo (QTime::currentTime()));

	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->getVertex (i);
				rotateVertex (v, rotpoint, transform);
				obj->setVertex (i, v);
			}
		}
		elif (obj->hasMatrix())
		{
			LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);

			// Transform the position
			Vertex v = mo->getPosition();
			rotateVertex (v, rotpoint, transform);
			mo->setPosition (v);

			// Transform the matrix
			mo->setTransform (transform * mo->getTransform());
		}
		elif (obj->getType() == 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->getPosition();
			Matrix t = mo->getTransform();

			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->getVertex (i);

				for_axes (ax)
					roundToDecimals (v[ax], 3);

				obj->setVertex (i, v);
				R()->compileObject (obj);
				num += 3;
			}
		}
	}

	log (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->getType() == LDObject::ELine || obj->getType() == LDObject::ECondLine)
			col = edgecolor;

		obj->setColor (col);
		R()->compileObject (obj);
		num++;
	}

	log (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->getVertex (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);
		}
	}

	log (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->getVertex (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->getType() != LDObject::ECondLine)
			continue;

		LDLine* repl = static_cast<LDCondLine*> (obj)->demote();
		R()->compileObject (repl);
		++num;
	}

	log (tr ("Demoted %1 conditional lines"), num);
	refresh();
}

// =============================================================================
// -----------------------------------------------------------------------------
static bool isColorUsed (int colnum)
{
	for (LDObject* obj : getCurrentDocument()->getObjects())
		if (obj->isColored() && obj->getColor() == 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)
	{
		log (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);
	}

	log (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 = fmt ("!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 && 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->getIndex() : 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