#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::unique_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 final : public TUndo {
QMimeData *m_oldData;
QMimeData *m_newData;
public:
CopyStrokesUndo(QMimeData *oldData, QMimeData *newData)
: m_oldData(oldData), m_newData(newData) {}
void undo() const override {
QClipboard *clipboard = QApplication::clipboard();
clipboard->setMimeData(cloneData(m_oldData), QClipboard::Clipboard);
}
void redo() const override {
QClipboard *clipboard = QApplication::clipboard();
clipboard->setMimeData(cloneData(m_newData), QClipboard::Clipboard);
}
int getSize() const override { return sizeof(*this); }
};
//=============================================================================
// PasteStrokesUndo
//-----------------------------------------------------------------------------
class PasteStrokesUndo final : 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 override {
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 override {
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 override { return sizeof(*this); }
};
//--------------------------------------------------------------------
class RemoveEndpointsUndo final : 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 override {
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 override {
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 override { 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 override {
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 override {
TVectorImageP image = m_level->getFrame(m_frameId, true);
std::set<int> indexes = m_indexes;
deleteStrokesWithoutUndo(image, indexes);
}
int getSize() const override { return sizeof(*this); }
};
//=============================================================================
// CutStrokesUndo
//-----------------------------------------------------------------------------
class CutStrokesUndo final : 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 override {
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;
}
//=============================================================================
//
// selectAll
//
//-----------------------------------------------------------------------------
void StrokeSelection::selectAll() {
if (!m_vi) return;
int sCount = int(m_vi->getStrokeCount());
for (int s = 0; s < sCount; ++s) {
m_indexes.insert(s);
}
StrokeSelection *selection = dynamic_cast<StrokeSelection *>(
TTool::getApplication()->getCurrentSelection()->getSelection());
if (selection) selection->notifyView();
}
//=============================================================================
//
// 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);
enableCommand(this, MI_SelectAll, &StrokeSelection::selectAll);
}
//===================================================================
namespace {
//===================================================================
class UndoSetStrokeStyle final : 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 override {
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 override {
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 override {
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);
}