src/gui_editactions.cc

Wed, 08 Jan 2014 13:57:10 +0200

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Wed, 08 Jan 2014 13:57:10 +0200
changeset 608
487db37f0bb3
parent 606
3dd6f343ec06
child 614
aac6f0021070
permissions
-rw-r--r--

- if loading another file to replace an explicitly loaded file, this file won't get closed automatically and thus needs to be manually closed. We also need to check that it's safe to close before doing this. Also fixed a rather argh problem with ::save not using the proper path...

/*
 *  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 "gui.h"
#include "main.h"
#include "document.h"
#include "colorSelectDialog.h"
#include "misc.h"
#include "widgets.h"
#include "gldraw.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()
{
	QList<LDObject*> 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)
{
	QList<LDObject*> 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)
			continue;

		QList<LDObject*> objs;

		if (obj->getType() == LDObject::ESubfile)
			objs = static_cast<LDSubfile*> (obj)->inlineContents (
					   (LDSubfile::InlineFlags)
					   ( (deep) ? LDSubfile::DeepInline : 0) |
					   LDSubfile::CacheInline
				   );
		else
			continue;

		// 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)
{
	QList<LDObject*> 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;

	QList<LDObject*> 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))
{
	QList<LDObject*> 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)
{
	QList<LDObject*> 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))
{
	QList<LDObject*> 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)
{
	QList<LDObject*> 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)
{
	QList<LDObject*> 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