Blob Blame Raw


#include "tools/strokeselection.h"

// TnzTools includes
#include "tools/imagegrouping.h"
#include "tools/toolhandle.h"
#include "tools/tool.h"
#include "tools/toolutils.h"

// TnzQt includes
#include "toonzqt/selectioncommandids.h"
#include "toonzqt/imageutils.h"
#include "toonzqt/tselectionhandle.h"
#include "toonzqt/strokesdata.h"
#include "toonzqt/rasterimagedata.h"
#include "toonzqt/dvdialog.h"

// TnzLib includes
#include "toonz/tpalettehandle.h"
#include "toonz/palettecontroller.h"
#include "toonz/tobjecthandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tscenehandle.h"

#include "toonz/tcenterlinevectorizer.h"
#include "toonz/stage.h"
#include "toonz/tstageobject.h"
#include "toonz/toonzscene.h"
#include "toonz/sceneproperties.h"

// TnzCore includes
#include "tthreadmessage.h"
#include "tundo.h"
#include "tstroke.h"
#include "tvectorimage.h"
#include "tcolorstyles.h"
#include "tpalette.h"

// Qt includes
#include <QApplication>
#include <QClipboard>

//=============================================================================
namespace
{

void vectorizeToonzImageData(const TVectorImageP &image, const ToonzImageData *tiData,
							 std::set<int> &indexes, TPalette *palette,
							 const VectorizerConfiguration &config)
{
	if (!tiData)
		return;
	QApplication::setOverrideCursor(Qt::WaitCursor);
	TRasterP ras;
	std::vector<TRectD> rects;
	std::vector<TStroke> strokes;
	std::vector<TStroke> originalStrokes;
	TAffine affine;
	double dpiX, dpiY;
	tiData->getData(ras, dpiX, dpiY, rects, strokes, originalStrokes, affine, image->getPalette());
	TRasterCM32P rasCM = ras;
	TToonzImageP ti(rasCM, rasCM->getBounds());
	VectorizerCore vc;
	TVectorImageP vi = vc.vectorize(ti, config, palette);
	assert(vi);
	vi->setPalette(palette);

	TScale sc(dpiX / Stage::inch, dpiY / Stage::inch);
	int i;
	TRectD selectionBounds;
	for (i = 0; i < (int)rects.size(); i++)
		selectionBounds += rects[i];
	for (i = 0; i < (int)strokes.size(); i++)
		selectionBounds += strokes[i].getBBox();
	TTranslation tr(selectionBounds.getP00());

	for (i = 0; i < (int)vi->getStrokeCount(); i++) {
		TStroke *stroke = vi->getStroke(i);
		stroke->transform(sc.inv() * affine * tr, true);
	}

	UINT oldImageSize = image->getStrokeCount();
	image->mergeImage(vi, TAffine());
	UINT newImageSize = image->getStrokeCount();
	indexes.clear();
	for (UINT sI = oldImageSize; sI < newImageSize; sI++)
		indexes.insert(sI);
	QApplication::restoreOverrideCursor();
}

//-----------------------------------------------------------------------------

void copyStrokesWithoutUndo(TVectorImageP image, std::set<int> &indexes)
{
	QClipboard *clipboard = QApplication::clipboard();
	StrokesData *data = new StrokesData();
	data->setImage(image, indexes);
	clipboard->setMimeData(data, QClipboard::Clipboard);
}

//-----------------------------------------------------------------------------

bool pasteStrokesWithoutUndo(TVectorImageP image, std::set<int> &outIndexes,
							 TSceneHandle *sceneHandle, bool insert = true)
{
	QMutexLocker lock(image->getMutex());
	QClipboard *clipboard = QApplication::clipboard();
	const StrokesData *stData = dynamic_cast<const StrokesData *>(clipboard->mimeData());
	const ToonzImageData *tiData = dynamic_cast<const ToonzImageData *>(clipboard->mimeData());
	const FullColorImageData *fciData = dynamic_cast<const FullColorImageData *>(clipboard->mimeData());
	std::set<int> indexes = outIndexes;
	if (stData)
		stData->getImage(image, indexes, insert);
	else if (tiData) {
		ToonzScene *scene = sceneHandle->getScene();
		assert(scene);

		const VectorizerParameters *vParams = scene->getProperties()->getVectorizerParameters();
		assert(vParams);

		std::auto_ptr<VectorizerConfiguration> config(vParams->getCurrentConfiguration(0.0));
		vectorizeToonzImageData(image, tiData, indexes, image->getPalette(), *config);
	} else if (fciData) {
		DVGui::error(QObject::tr("The copied selection cannot be pasted in the current drawing."));
		return false;
	} else
		return false;

	StrokeSelection *selection = dynamic_cast<StrokeSelection *>(TTool::getApplication()->getCurrentSelection()->getSelection());
	if (selection)
		selection->notifyView();
	outIndexes = indexes; //outIndexes is a reference  to current  selection, so the notifyImageChanged could reset it!
	return true;
}

//-----------------------------------------------------------------------------

void deleteStrokesWithoutUndo(TVectorImageP image, std::set<int> &indexes)
{
	QMutexLocker lock(image->getMutex());
	std::vector<int> indexesV(indexes.begin(), indexes.end());
	TRectD bbox;
	UINT i = 0;
	for (; i < indexesV.size(); i++)
		bbox += image->getStroke(indexesV[i])->getBBox();

	std::vector<TFilledRegionInf> regions;
	ImageUtils::getFillingInformationOverlappingArea(image, regions, bbox);

	TVectorImageP other = image->splitImage(indexesV, true);

	indexes.clear();

	TTool::getApplication()->getCurrentTool()->getTool()->notifyImageChanged();
	StrokeSelection *selection = dynamic_cast<StrokeSelection *>(TTool::getApplication()->getCurrentSelection()->getSelection());
	if (selection)
		selection->notifyView();
}

//-----------------------------------------------------------------------------

void cutStrokesWithoutUndo(TVectorImageP image, std::set<int> &indexes)
{
	copyStrokesWithoutUndo(image, indexes);
	deleteStrokesWithoutUndo(image, indexes);
}

//=============================================================================
// CopyStrokesUndo
//-----------------------------------------------------------------------------

class CopyStrokesUndo : public TUndo
{
	QMimeData *m_oldData;
	QMimeData *m_newData;

public:
	CopyStrokesUndo(QMimeData *oldData,
					QMimeData *newData)
		: m_oldData(oldData), m_newData(newData)
	{
	}

	void undo() const
	{
		QClipboard *clipboard = QApplication::clipboard();
		clipboard->setMimeData(cloneData(m_oldData), QClipboard::Clipboard);
	}

	void redo() const
	{
		QClipboard *clipboard = QApplication::clipboard();
		clipboard->setMimeData(cloneData(m_newData), QClipboard::Clipboard);
	}

	int getSize() const
	{
		return sizeof(*this);
	}
};

//=============================================================================
// PasteStrokesUndo
//-----------------------------------------------------------------------------

class PasteStrokesUndo : public TUndo
{
	TXshSimpleLevelP m_level;
	TFrameId m_frameId;
	std::set<int> m_indexes;
	TPaletteP m_oldPalette;
	QMimeData *m_oldData;
	TSceneHandle *m_sceneHandle;

public:
	PasteStrokesUndo(TXshSimpleLevel *level,
					 const TFrameId &frameId,
					 std::set<int> &indexes,
					 TPaletteP oldPalette,
					 TSceneHandle *sceneHandle)
		: m_level(level), m_frameId(frameId), m_indexes(indexes), m_oldPalette(oldPalette), m_sceneHandle(sceneHandle)
	{
		QClipboard *clipboard = QApplication::clipboard();
		m_oldData = cloneData(clipboard->mimeData());
	}

	~PasteStrokesUndo()
	{
		delete m_oldData;
	}

	void undo() const
	{
		TVectorImageP image = m_level->getFrame(m_frameId, true);

		// Se la selezione corrente e' la stroke selection devo svuotarla,
		// altrimenti puo' rimanere selezionato uno stroke che non esiste piu'.
		StrokeSelection *selection = dynamic_cast<StrokeSelection *>(TTool::getApplication()->getCurrentSelection()->getSelection());
		if (selection)
			selection->selectNone();

		std::set<int> indexes = m_indexes;
		deleteStrokesWithoutUndo(image, indexes);
	}

	void redo() const
	{
		TVectorImageP image = m_level->getFrame(m_frameId, true);
		std::set<int> indexes = m_indexes;

		QClipboard *clipboard = QApplication::clipboard();
		QMimeData *data = cloneData(clipboard->mimeData());

		clipboard->setMimeData(cloneData(m_oldData), QClipboard::Clipboard);

		pasteStrokesWithoutUndo(image, indexes, m_sceneHandle);
		TTool::getApplication()->getCurrentTool()->getTool()->notifyImageChanged();

		clipboard->setMimeData(data, QClipboard::Clipboard);
	}

	int getSize() const
	{
		return sizeof(*this);
	}
};

//--------------------------------------------------------------------

class RemoveEndpointsUndo : public TUndo
{
	TXshSimpleLevelP m_level;
	TFrameId m_frameId;
	std::vector<std::pair<int, TStroke *>> m_strokes;

public:
	RemoveEndpointsUndo(TXshSimpleLevel *level,
						const TFrameId &frameId,
						std::vector<std::pair<int, TStroke *>> strokes)
		: m_level(level), m_frameId(frameId), m_strokes(strokes)

	{
	}

	~RemoveEndpointsUndo()
	{
		int i;
		for (i = 0; i < (int)m_strokes.size(); i++)
			delete m_strokes[i].second;
	}

	void undo() const
	{
		TVectorImageP vi = m_level->getFrame(m_frameId, true);
		int i;
		for (i = 0; i < (int)m_strokes.size(); i++) {
			TStroke *newS = new TStroke(*(m_strokes[i].second));
			newS->setId(m_strokes[i].second->getId());
			vi->restoreEndpoints(m_strokes[i].first, newS);
		}
		StrokeSelection *selection = dynamic_cast<StrokeSelection *>(TTool::getApplication()->getCurrentSelection()->getSelection());
		if (selection)
			selection->selectNone();

		TTool::getApplication()->getCurrentTool()->getTool()->notifyImageChanged();
	}

	void redo() const
	{
		int i;
		TVectorImageP vi = m_level->getFrame(m_frameId, true);
		for (i = 0; i < (int)m_strokes.size(); i++) {
			TStroke *s = vi->removeEndpoints(m_strokes[i].first);
			delete s;
			//assert(s==m_strokes[i].second);
		}
		TTool::getApplication()->getCurrentTool()->getTool()->notifyImageChanged();
	}

	int getSize() const
	{
		return sizeof(*this);
	}
};
//=============================================================================
// DeleteFramesUndo
//-----------------------------------------------------------------------------

class DeleteStrokesUndo : public TUndo
{
protected:
	TXshSimpleLevelP m_level;
	TFrameId m_frameId;
	std::set<int> m_indexes;
	QMimeData *m_data;
	TSceneHandle *m_sceneHandle;

public:
	DeleteStrokesUndo(TXshSimpleLevel *level,
					  const TFrameId &frameId,
					  std::set<int> indexes, QMimeData *data,
					  TSceneHandle *sceneHandle)
		: m_level(level), m_frameId(frameId), m_indexes(indexes), m_data(data), m_sceneHandle(sceneHandle)
	{
	}

	~DeleteStrokesUndo()
	{
		delete m_data;
	}

	void undo() const
	{
		QClipboard *clipboard = QApplication::clipboard();
		QMimeData *oldData = cloneData(clipboard->mimeData());

		clipboard->setMimeData(cloneData(m_data), QClipboard::Clipboard);
		std::set<int> indexes = m_indexes;
		TVectorImageP image = m_level->getFrame(m_frameId, true);
		pasteStrokesWithoutUndo(image, indexes, m_sceneHandle, false);
		TTool::getApplication()->getCurrentTool()->getTool()->notifyImageChanged();

		clipboard->setMimeData(oldData, QClipboard::Clipboard);
	}

	void redo() const
	{
		TVectorImageP image = m_level->getFrame(m_frameId, true);
		std::set<int> indexes = m_indexes;
		deleteStrokesWithoutUndo(image, indexes);
	}

	int getSize() const
	{
		return sizeof(*this);
	}
};

//=============================================================================
// CutStrokesUndo
//-----------------------------------------------------------------------------

class CutStrokesUndo : public DeleteStrokesUndo
{

public:
	CutStrokesUndo(TXshSimpleLevel *level,
				   const TFrameId &frameId,
				   std::set<int> indexes, QMimeData *data,
				   TSceneHandle *sceneHandle)
		: DeleteStrokesUndo(level, frameId, indexes, data, sceneHandle)
	{
	}

	~CutStrokesUndo()
	{
	}

	void redo() const
	{
		TVectorImageP image = m_level->getFrame(m_frameId, true);
		std::set<int> indexes = m_indexes;
		cutStrokesWithoutUndo(image, indexes);
	}
};

} // namespace

//=============================================================================
//
// StrokeSelection ctor/dtor
//
//-----------------------------------------------------------------------------

StrokeSelection::StrokeSelection()
	: m_groupCommand(new TGroupCommand()), m_sceneHandle(), m_updateSelectionBBox(false)
{
	m_groupCommand->setSelection(this);
}

//-----------------------------------------------------------------------------

StrokeSelection::~StrokeSelection()
{
}

//-----------------------------------------------------------------------------

StrokeSelection::StrokeSelection(const StrokeSelection &other)
	: m_vi(other.m_vi), m_indexes(other.m_indexes), m_groupCommand(new TGroupCommand()), m_sceneHandle(other.m_sceneHandle), m_updateSelectionBBox(other.m_updateSelectionBBox)
{
	m_groupCommand->setSelection(this);
}

//-----------------------------------------------------------------------------

StrokeSelection &StrokeSelection::operator=(const StrokeSelection &other)
{
	m_vi = other.m_vi;
	m_indexes = other.m_indexes;
	m_sceneHandle = other.m_sceneHandle;
	m_updateSelectionBBox = other.m_updateSelectionBBox;

	return *this;
}

//-----------------------------------------------------------------------------

void StrokeSelection::select(int index, bool on)
{
	if (on)
		m_indexes.insert(index);
	else
		m_indexes.erase(index);
}

//-----------------------------------------------------------------------------

void StrokeSelection::toggle(int index)
{
	std::set<int>::iterator it = m_indexes.find(index);
	if (it == m_indexes.end())
		m_indexes.insert(index);
	else
		m_indexes.erase(it);
}

//=============================================================================
//
// removeEndpoints
//
//-----------------------------------------------------------------------------

void StrokeSelection::removeEndpoints()
{
	if (!m_vi)
		return;
	if (m_indexes.empty())
		return;

	std::vector<std::pair<int, TStroke *>> undoData;

	m_vi->findRegions();
	set<int>::iterator it = m_indexes.begin();
	for (; it != m_indexes.end(); ++it) {
		TStroke *s = m_vi->removeEndpoints(*it);
		if (s)
			undoData.push_back(std::pair<int, TStroke *>(*it, s));
	}
	TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
	TXshSimpleLevel *level = TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
	if (!undoData.empty())
		TUndoManager::manager()->add(new RemoveEndpointsUndo(level, tool->getCurrentFid(), undoData));

	m_updateSelectionBBox = true;
	tool->notifyImageChanged();
	m_updateSelectionBBox = false;
}

//=============================================================================
//
// deleteStrokes
//
//-----------------------------------------------------------------------------

void StrokeSelection::deleteStrokes()
{
	if (!m_vi)
		return;
	if (m_indexes.empty())
		return;
	TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
	if (!tool)
		return;

	bool isSpline = tool->getApplication()->getCurrentObject()->isSpline();
	TUndo *undo;
	if (isSpline)
		undo = new ToolUtils::UndoPath(tool->getXsheet()->getStageObject(tool->getObjectId())->getSpline());

	StrokesData *data = new StrokesData();
	data->setImage(m_vi, m_indexes);
	std::set<int> oldIndexes = m_indexes;
	deleteStrokesWithoutUndo(m_vi, m_indexes);

	if (!isSpline) {
		TXshSimpleLevel *level = TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
		TUndoManager::manager()->add(new DeleteStrokesUndo(level, tool->getCurrentFid(), oldIndexes, data, m_sceneHandle));
	} else {
		assert(undo);
		if (undo)
			TUndoManager::manager()->add(undo);
	}
}

//=============================================================================
//
// StrokeSelection::copy()
//
//-----------------------------------------------------------------------------

void StrokeSelection::copy()
{
	if (m_indexes.empty())
		return;
	QClipboard *clipboard = QApplication::clipboard();
	QMimeData *oldData = cloneData(clipboard->mimeData());
	copyStrokesWithoutUndo(m_vi, m_indexes);
	QMimeData *newData = cloneData(clipboard->mimeData());

	//TUndoManager::manager()->add(new CopyStrokesUndo(oldData, newData));
}

//=============================================================================
//
// StrokeSelection::paste()
//
//-----------------------------------------------------------------------------

void StrokeSelection::paste()
{
	TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
	if (!tool)
		return;
	if (TTool::getApplication()->getCurrentObject()->isSpline()) {
		const StrokesData *stData = dynamic_cast<const StrokesData *>(QApplication::clipboard()->mimeData());
		if (!stData)
			return;
		TVectorImageP splineImg = tool->getImage(true);
		TVectorImageP img = stData->m_image;
		if (!splineImg || !img)
			return;

		QMutexLocker lock(splineImg->getMutex());
		TUndo *undo = new ToolUtils::UndoPath(tool->getXsheet()->getStageObject(tool->getObjectId())->getSpline());
		while (splineImg->getStrokeCount() > 0)
			splineImg->deleteStroke(0);

		TStroke *stroke = img->getStroke(0);
		splineImg->addStroke(new TStroke(*stroke), false);
		TUndoManager::manager()->add(undo);
		tool->notifyImageChanged();
		tool->invalidate();
		return;
	}

	TVectorImageP tarImg = tool->touchImage();
	if (!tarImg)
		return;
	TPaletteP palette = tarImg->getPalette();
	TPaletteP oldPalette = new TPalette();
	if (palette)
		oldPalette = palette->clone();
	bool isPaste = pasteStrokesWithoutUndo(tarImg, m_indexes, m_sceneHandle);
	if (isPaste) {
		TXshSimpleLevel *level = TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
		TUndoManager::manager()->add(new PasteStrokesUndo(level, tool->getCurrentFid(), m_indexes, oldPalette, m_sceneHandle));
		m_updateSelectionBBox = isPaste;
	}
	tool->notifyImageChanged();
	tool->getApplication()->getPaletteController()->getCurrentLevelPalette()->notifyPaletteChanged();
	m_updateSelectionBBox = false;
	tool->invalidate();
}

//=============================================================================
//
// StrokeSelection::cut()
//
//-----------------------------------------------------------------------------

void StrokeSelection::cut()
{
	if (m_indexes.empty())
		return;
	TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
	if (!tool)
		return;

	bool isSpline = tool->getApplication()->getCurrentObject()->isSpline();
	TUndo *undo;
	if (isSpline)
		undo = new ToolUtils::UndoPath(tool->getXsheet()->getStageObject(tool->getObjectId())->getSpline());

	StrokesData *data = new StrokesData();
	data->setImage(m_vi, m_indexes);
	std::set<int> oldIndexes = m_indexes;
	cutStrokesWithoutUndo(m_vi, m_indexes);
	if (!isSpline) {
		TXshSimpleLevel *level = tool->getApplication()->getCurrentLevel()->getSimpleLevel();
		TUndoManager::manager()->add(new CutStrokesUndo(level, tool->getCurrentFid(), oldIndexes, data, m_sceneHandle));
	} else {
		assert(undo);
		if (undo)
			TUndoManager::manager()->add(undo);
	}
}

//=============================================================================
//
// enableCommands
//
//-----------------------------------------------------------------------------

void StrokeSelection::enableCommands()
{
	enableCommand(this, MI_Clear, &StrokeSelection::deleteStrokes);
	enableCommand(this, MI_Cut, &StrokeSelection::cut);
	enableCommand(this, MI_Copy, &StrokeSelection::copy);
	enableCommand(this, MI_Paste, &StrokeSelection::paste);

	enableCommand(m_groupCommand.get(), MI_Group, &TGroupCommand::group);
	enableCommand(m_groupCommand.get(), MI_Ungroup, &TGroupCommand::ungroup);
	enableCommand(m_groupCommand.get(), MI_BringToFront, &TGroupCommand::front);
	enableCommand(m_groupCommand.get(), MI_BringForward, &TGroupCommand::forward);
	enableCommand(m_groupCommand.get(), MI_SendBack, &TGroupCommand::back);
	enableCommand(m_groupCommand.get(), MI_SendBackward, &TGroupCommand::backward);
	enableCommand(m_groupCommand.get(), MI_EnterGroup, &TGroupCommand::enterGroup);
	enableCommand(m_groupCommand.get(), MI_ExitGroup, &TGroupCommand::exitGroup);

	enableCommand(this, MI_RemoveEndpoints, &StrokeSelection::removeEndpoints);
}

//===================================================================
namespace
{
//===================================================================

class UndoSetStrokeStyle : public TUndo
{
	TVectorImageP m_image;
	std::vector<int> m_strokeIndexes;
	std::vector<int> m_oldStyles;
	int m_newStyle;

public:
	UndoSetStrokeStyle(TVectorImageP image, int newStyle)
		: m_image(image), m_newStyle(newStyle)
	{
	}

	void addStroke(TStroke *stroke)
	{
		m_strokeIndexes.push_back(m_image->getStrokeIndex(stroke));
		m_oldStyles.push_back(stroke->getStyle());
	}

	void undo() const
	{
		UINT size = m_strokeIndexes.size();
		assert(size == m_oldStyles.size());

		for (UINT i = 0; i != size; i++) {
			int index = m_strokeIndexes[i];
			if (index != -1 && index < (int)m_image->getStrokeCount())
				m_image->getStroke(index)->setStyle(m_oldStyles[i]);
		}

		TTool::getApplication()->getCurrentTool()->getTool()->notifyImageChanged();
	}

	void redo() const
	{
		UINT size = m_strokeIndexes.size();
		assert(size == m_oldStyles.size());

		for (UINT i = 0; i != size; i++) {
			int index = m_strokeIndexes[i];
			if (index != -1 && index < (int)m_image->getStrokeCount())
				m_image->getStroke(index)->setStyle(m_newStyle);
		}

		TTool::getApplication()->getCurrentTool()->getTool()->notifyImageChanged();
	}

	int getSize() const
	{
		return sizeof(*this) +
			   m_strokeIndexes.capacity() * sizeof(m_strokeIndexes[0]) +
			   m_oldStyles.capacity() * sizeof(m_oldStyles[0]);
	}
};

} // namespace

//=============================================================================
//
// changeColorStyle
//
//-----------------------------------------------------------------------------

void StrokeSelection::changeColorStyle(int styleIndex)
{
	TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
	if (!tool)
		return;
	TVectorImageP img(tool->getImage(true));
	if (!img)
		return;
	TPalette *palette = img->getPalette();
	TColorStyle *cs = palette->getStyle(styleIndex);
	if (!cs->isStrokeStyle())
		return;
	if (m_indexes.empty())
		return;

	UndoSetStrokeStyle *undo = new UndoSetStrokeStyle(img, styleIndex);
	std::set<int>::iterator it;
	for (it = m_indexes.begin(); it != m_indexes.end(); ++it) {
		int index = *it;
		assert(0 <= index && index < (int)img->getStrokeCount());
		TStroke *stroke = img->getStroke(index);
		undo->addStroke(stroke);
		stroke->setStyle(styleIndex);
	}

	tool->notifyImageChanged();
	TUndoManager::manager()->add(undo);
}