#include "tools/imagegrouping.h"
// TnzTools includes
#include "tools/strokeselection.h"
#include "tools/tool.h"
#include "tools/toolhandle.h"
#include "tools/toolutils.h"
#include "vectorselectiontool.h"
// TnzQt includes
#include "toonzqt/tselectionhandle.h"
#include "toonzqt/selectioncommandids.h"
// TnzLib includes
#include "toonz/tscenehandle.h"
#include "toonz/txshlevelhandle.h"
// TnzCore includes
#include "tvectorimage.h"
#include "tundo.h"
#include "tthreadmessage.h"
// Qt includes
#include <QMenu>
//=============================================================================
namespace
{
//-----------------------------------------------------------------------------
void groupWithoutUndo(TVectorImage *vimg, StrokeSelection *selection)
{
int count = 0, fromStroke = -1, lastSelected = -1;
for (int i = 0; i < (int)vimg->getStrokeCount(); i++)
if (selection->isSelected(i)) {
if (fromStroke == -1)
fromStroke = i;
else if (lastSelected != i - 1) //non sono contigui gli stroke selezionati: faccio affiorare quelli sotto
{
int j = 0;
for (j = 0; j < count; j++)
selection->select(fromStroke + j, false);
vimg->moveStrokes(fromStroke, count, i);
fromStroke = i - count;
for (j = 0; j < count; j++)
selection->select(fromStroke + j, true);
}
lastSelected = i;
count++;
}
assert(count > 0);
vimg->group(fromStroke, count);
TTool::getApplication()->getCurrentTool()->getTool()->notifyImageChanged();
}
//-----------------------------------------------------------------------------
void ungroupWithoutUndo(TVectorImage *vimg, StrokeSelection *selection)
{
for (int i = 0; i < (int)vimg->getStrokeCount();)
if (selection->isSelected(i)) {
if (!vimg->isStrokeGrouped(i)) {
i++;
continue;
}
i += vimg->ungroup(i);
} else
i++;
TTool::getApplication()->getCurrentTool()->getTool()->notifyImageChanged();
}
//=============================================================================
// GroupUndo
//-----------------------------------------------------------------------------
class GroupUndo : public ToolUtils::TToolUndo
{
std::auto_ptr<StrokeSelection> m_selection;
public:
GroupUndo(TXshSimpleLevel *level, const TFrameId &frameId, StrokeSelection *selection)
: ToolUtils::TToolUndo(level, frameId), m_selection(selection) {}
void undo() const
{
TVectorImageP image = m_level->getFrame(m_frameId, true);
if (image)
ungroupWithoutUndo(image.getPointer(), m_selection.get());
}
void redo() const
{
TVectorImageP image = m_level->getFrame(m_frameId, true);
if (image)
groupWithoutUndo(image.getPointer(), m_selection.get());
}
int getSize() const
{
return sizeof(*this);
}
QString getToolName()
{
return QObject::tr("Group");
}
};
//=============================================================================
// UngroupUndo
//-----------------------------------------------------------------------------
class UngroupUndo : public ToolUtils::TToolUndo
{
std::auto_ptr<StrokeSelection> m_selection;
public:
UngroupUndo(TXshSimpleLevel *level, const TFrameId &frameId, StrokeSelection *selection)
: ToolUtils::TToolUndo(level, frameId), m_selection(selection) {}
void undo() const
{
TVectorImageP image = m_level->getFrame(m_frameId, true);
if (image)
groupWithoutUndo(image.getPointer(), m_selection.get());
}
void redo() const
{
TVectorImageP image = m_level->getFrame(m_frameId, true);
if (image)
ungroupWithoutUndo(image.getPointer(), m_selection.get());
}
int getSize() const
{
return sizeof(*this);
}
QString getToolName()
{
return QObject::tr("Ungroup");
}
};
//=============================================================================
// MoveGroupUndo
//-----------------------------------------------------------------------------
class MoveGroupUndo : public ToolUtils::TToolUndo
{
UCHAR m_moveType;
int m_refStroke, m_count, m_moveBefore;
std::vector<std::pair<TStroke *, int>> m_selectedGroups;
public:
MoveGroupUndo(TXshSimpleLevel *level, const TFrameId &frameId, UCHAR moveType,
int refStroke, int count, int moveBefore, const std::vector<std::pair<TStroke *, int>> &selectedGroups)
: ToolUtils::TToolUndo(level, frameId), m_moveType(moveType), m_refStroke(refStroke), m_count(count), m_moveBefore(moveBefore), m_selectedGroups(selectedGroups)
{
}
~MoveGroupUndo()
{
}
void undo() const
{
int refStroke;
int moveBefore;
switch (m_moveType) {
case TGroupCommand::FRONT:
refStroke = m_moveBefore - m_count;
moveBefore = m_refStroke;
break;
case TGroupCommand::FORWARD:
refStroke = m_moveBefore - m_count;
moveBefore = m_refStroke;
break;
case TGroupCommand::BACK:
refStroke = m_moveBefore;
moveBefore = m_refStroke + m_count;
break;
case TGroupCommand::BACKWARD:
refStroke = m_moveBefore;
moveBefore = m_refStroke + m_count;
break;
default:
assert(!"group move not defined!");
break;
}
TVectorImageP image = m_level->getFrame(m_frameId, true);
if (!image)
return;
QMutexLocker lock(image->getMutex());
image->moveStrokes(refStroke, m_count, moveBefore);
StrokeSelection *selection = dynamic_cast<StrokeSelection *>(TTool::getApplication()->getCurrentSelection()->getSelection());
if (selection) { //Se la selezione corrente e' la StrokeSelection
// seleziono gli stroke che ho modificato con l'undo
selection->selectNone();
for (int i = 0; i < (int)m_selectedGroups.size(); i++) {
int index = image->getStrokeIndex(m_selectedGroups[i].first);
if (index == -1)
continue;
for (int j = index; j < index + m_selectedGroups[i].second; j++)
selection->select(j, true);
}
}
TTool::getApplication()->getCurrentScene()->notifySceneChanged();
notifyImageChanged();
}
void redo() const
{
TVectorImageP image = m_level->getFrame(m_frameId, true);
if (!image)
return;
QMutexLocker lock(image->getMutex());
image->moveStrokes(m_refStroke, m_count, m_moveBefore);
StrokeSelection *selection = dynamic_cast<StrokeSelection *>(TTool::getApplication()->getCurrentSelection()->getSelection());
if (selection) { //Se la selezione corrente e' la StrokeSelection
// seleziono gli stroke che ho modificato con il redo
selection->selectNone();
for (int i = 0; i < (int)m_selectedGroups.size(); i++) {
int index = image->getStrokeIndex(m_selectedGroups[i].first);
if (index == -1)
continue;
for (int j = index; j < index + m_selectedGroups[i].second; j++)
selection->select(j, true);
}
}
TTool::getApplication()->getCurrentScene()->notifySceneChanged();
notifyImageChanged();
}
int getSize() const
{
return sizeof(*this);
}
QString getToolName()
{
return QObject::tr("Move Group");
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// TGroupCommand
//-----------------------------------------------------------------------------
namespace
{
std::vector<std::pair<TStroke *, int>> getSelectedGroups(TVectorImage *vimg, StrokeSelection *sel)
{
UINT i, j;
std::vector<std::pair<TStroke *, int>> ret;
for (i = 0; i < vimg->getStrokeCount(); i++)
if (sel->isSelected(i)) {
if (vimg->isStrokeGrouped(i)) {
for (j = i + 1; j < vimg->getStrokeCount() && vimg->sameSubGroup(i, j); j++)
if (!sel->isSelected(j))
return std::vector<std::pair<TStroke *, int>>();
ret.push_back(std::pair<TStroke *, int>(vimg->getStroke(i), j - i));
i = j - 1;
} else
ret.push_back(std::pair<TStroke *, int>(vimg->getStroke(i), 1));
}
return ret;
}
} //namepsace
//--------------------------------------------------------------------------------------
UCHAR TGroupCommand::getGroupingOptions()
{
TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
if (!tool)
return 0;
TVectorImage *vimg = dynamic_cast<TVectorImage *>(tool->getImage(false));
if (!vimg)
return 0;
UCHAR mask = 0;
int count = 0;
UINT i, j;
//spostamento: si possono spostare solo gruppi interi oppure stroke non gruppate
std::vector<std::pair<TStroke *, int>> strokeIndexes = getSelectedGroups(vimg, m_sel);
/*
//spostamento: si puo' spostare solo un gruppo(e uno solo per volta) oppure una stroke singola non gruppata
for (i=0; i<vimg->getStrokeCount(); i++)
if (m_sel->isSelected(i))
{
if (strokeIndex != -1)
{
if (!vimg->isStrokeGrouped(i) || !vimg->sameSubGroup(strokeIndex, i))
break;
}
else
{
strokeIndex = i;
if (vimg->isStrokeGrouped(i))
{
for (j=0; j<vimg->getStrokeCount(); j++)
if (!m_sel->isSelected(j) && vimg->sameSubGroup(i, j))
break;//non tutto il gruppo e' selezionato
if (j<vimg->getStrokeCount())
break;
}
}
count++;
}
*/
if (strokeIndexes.empty())
return 0; //no stroke selected
int strokeIndex = vimg->getStrokeIndex(strokeIndexes[0].first);
if (strokeIndexes.size() > 1 || strokeIndex > 0) {
mask |= BACK;
mask |= BACKWARD;
}
if (strokeIndexes.size() > 1 || strokeIndex + strokeIndexes[0].second - 1 < (int)vimg->getStrokeCount() - 1) {
mask |= FRONT;
mask |= FORWARD;
}
/*
if (i == vimg->getStrokeCount())
{
if (strokeIndex+count<(int)vimg->getStrokeCount())
{
mask |= FRONT;
mask |= FORWARD;
}
if (strokeIndex>0)
{
mask |= BACK;
mask |= BACKWARD;
}
}
*/
//PER l'UNGROUP: si ungruppa solo se tutti gli stroke selezionati stanno nel gruppo (anche piu' gruppi insieme)
for (i = 0; i < vimg->getStrokeCount(); i++) {
if (m_sel->isSelected(i)) {
if (!vimg->isStrokeGrouped(i))
break;
for (j = 0; j < vimg->getStrokeCount(); j++)
if (!m_sel->isSelected(j) && vimg->sameSubGroup(i, j))
break;
if (j < vimg->getStrokeCount())
break;
}
}
if (i == vimg->getStrokeCount())
mask |= UNGROUP;
//PER il GROUP: si raggruppa solo se:
// //almeno una delle stroke selezionate non fa parte di gruppi o e' di un gruppo diverso
// e se c'e' una stroke di un gruppo, allora tutto il gruppo della stroke e' selezionato
bool groupingMakesSense = false;
int refStroke = -1;
for (i = 0; i < vimg->getStrokeCount(); i++)
if (m_sel->isSelected(i)) {
if (vimg->isStrokeGrouped(i)) {
if (refStroke == -1)
refStroke = i;
else if (!vimg->sameSubGroup(refStroke, i))
groupingMakesSense = true; //gli storke selezionati non sono gia' tutti dello stesso gruppo
for (j = 0; j < vimg->getStrokeCount(); j++)
if (!m_sel->isSelected(j) && vimg->sameGroup(i, j))
return mask;
} else
groupingMakesSense = true;
}
if (groupingMakesSense)
mask |= GROUP;
return mask;
}
//-----------------------------------------------------------------------------
#ifdef NUOVO
UCHAR TGroupCommand::getGroupingOptions()
{
TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
if (!tool)
return 0;
TVectorImage *vimg = dynamic_cast<TVectorImage *>(tool->getImage(false));
if (!vimg)
return 0;
int strokeIndex = -1;
UCHAR mask = 0;
int count = 0;
UINT i, j;
bool valid = true;
//spostamento: si puo' spostare solo un gruppo(e uno solo per volta) oppure una stroke singola non gruppata
std::vector<pair<int, int>> groups;
for (i = 0; i < vimg->getStrokeCount() && valid;)
if (m_sel->isSelected(i)) {
strokeIndex = i;
count = 1;
if (vimg->isStrokeGrouped(i)) {
for (j = i + 1; j < vimg->getStrokeCount() && valid; j++)
if (m_sel->isSelected(j) && vimg->sameSubGroup(i, j))
count++;
else if (!m_sel->isSelected(j) && vimg->sameSubGroup(i, j))
valid = false; //non tutto il gruppo e' selezionato
}
groups.push_back(pair<int, int>(strokeIndex, count));
i = i + count;
}
if (groups.empty())
return 0; //no stroke selected
if (!valid)
return 0;
if (strokeIndex + count < (int)vimg->getStrokeCount()) {
mask |= FRONT;
mask |= FORWARD;
}
if (strokeIndex > 0) {
mask |= BACK;
mask |= BACKWARD;
}
//PER l'UNGROUP: si ungruppa solo se tutti gli stroke selezionati stanno nel gruppo (anche piu' gruppi insieme)
for (i = 0; i < vimg->getStrokeCount(); i++) {
if (m_sel->isSelected(i)) {
if (!vimg->isStrokeGrouped(i))
break;
for (j = 0; j < vimg->getStrokeCount(); j++)
if (!m_sel->isSelected(j) && vimg->sameSubGroup(i, j))
break;
if (j < vimg->getStrokeCount())
break;
}
}
if (i == vimg->getStrokeCount())
mask |= UNGROUP;
//PER il GROUP: si raggruppa solo se:
// //almeno una delle stroke selezionate non fa parte di gruppi o e' di un gruppo diverso
// e se c'e' una stroke di un gruppo, allora tutto il gruppo della stroke e' selezionato
bool groupingMakesSense = false;
int refStroke = -1;
for (i = 0; i < vimg->getStrokeCount(); i++)
if (m_sel->isSelected(i)) {
if (vimg->isStrokeGrouped(i)) {
if (refStroke == -1)
refStroke = i;
else if (!vimg->sameSubGroup(refStroke, i))
groupingMakesSense = true; //gli storke selezionati non sono gia' tutti dello stesso gruppo
for (j = 0; j < vimg->getStrokeCount(); j++)
if (!m_sel->isSelected(j) && vimg->sameGroup(i, j))
return mask;
} else
groupingMakesSense = true;
}
if (groupingMakesSense)
mask |= GROUP;
return mask;
}
#endif
//-----------------------------------------------------------------------------
void TGroupCommand::group()
{
if (!(getGroupingOptions() & GROUP))
return;
TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
if (!tool)
return;
TVectorImage *vimg = (TVectorImage *)tool->getImage(true);
if (!vimg)
return;
QMutexLocker lock(vimg->getMutex());
groupWithoutUndo(vimg, m_sel);
TXshSimpleLevel *level = TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
TUndoManager::manager()->add(new GroupUndo(level, tool->getCurrentFid(), new StrokeSelection(*m_sel)));
}
//-----------------------------------------------------------------------------
void TGroupCommand::enterGroup()
{
TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
if (!tool)
return;
TVectorImage *vimg = (TVectorImage *)tool->getImage(true);
if (!vimg)
return;
int index = -1;
for (int i = 0; i < (int)vimg->getStrokeCount(); i++)
if (m_sel->isSelected(i)) {
index = i;
break;
}
if (index == -1)
return;
if (!vimg->canEnterGroup(index))
return;
vimg->enterGroup(index);
TSelection *selection = TSelection::getCurrent();
if (selection)
selection->selectNone();
TTool::getApplication()->getCurrentScene()->notifySceneChanged();
}
//-----------------------------------------------------------------------------
void TGroupCommand::exitGroup()
{
TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
if (!tool)
return;
TVectorImage *vimg = (TVectorImage *)tool->getImage(true);
if (!vimg)
return;
vimg->exitGroup();
TTool::getApplication()->getCurrentScene()->notifySceneChanged();
}
//-----------------------------------------------------------------------------
void TGroupCommand::ungroup()
{
if (!(getGroupingOptions() & UNGROUP))
return;
TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
if (!tool)
return;
TVectorImage *vimg = (TVectorImage *)tool->getImage(true);
if (!vimg)
return;
QMutexLocker lock(vimg->getMutex());
ungroupWithoutUndo(vimg, m_sel);
TXshSimpleLevel *level = TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
TUndoManager::manager()->add(new UngroupUndo(level, tool->getCurrentFid(), new StrokeSelection(*m_sel)));
}
//-----------------------------------------------------------------------------
/*
void computeMovingBounds(TVectorImage*vimg, int fromIndex, int toIndex, int&lower, int& upper)
{
lower = 0;
upper = vimg->getStrokeCount();
int refDepth = vimg->getGroupDepth(fromIndex)-1;
if (refDepth==0)
return;
int i;
for (i=fromIndex-1; i>=0; i--)
if (vimg->getCommonGroupDepth(fromIndex, i)<refDepth)
{
lower = i+1;
break;
}
for (i=fromIndex+1; i<vimg->getStrokeCount(); i++)
if (vimg->getCommonGroupDepth(fromIndex, i)<refDepth)
{
upper = i;
break;
}
}
*/
//-----------------------------------------------------------------------------
namespace
{
int commonDepth(TVectorImage *vimg, int index1, int count, int index2)
{
int i, ret = 1000;
for (i = index1; i < index1 + count; i++)
ret = std::min(ret, vimg->getCommonGroupDepth(i, index2));
return ret;
}
//-----------------------------------------------------------------------------
/*
bool cantMove1(TVectorImage* vimg, int refStroke, int count, int moveBefore, bool rev)
{
if (moveBefore<(int)vimg->getStrokeCount() && moveBefore>0 &&
vimg->getCommonGroupDepth(moveBefore-1, moveBefore)>commonDepth(vimg, refStroke, count, moveBefore) &&
vimg->getCommonGroupDepth(moveBefore-1, moveBefore)>commonDepth(vimg, refStroke, count, moveBefore-1))
return true;
int prev = (rev)?moveBefore:moveBefore-1;
if (refStroke>0 && commonDepth(vimg, refStroke, count, refStroke-1)>commonDepth(vimg, refStroke, count, prev))
return true;
if (refStroke+count<(int)vimg->getStrokeCount() && commonDepth(vimg, refStroke, count, refStroke+count)>commonDepth(vimg, refStroke, count, prev))
return true;
return false;
}
*/
} //namespace
//-----------------------------------------------------------------------------
/*
int i, refStroke=-1, count=0;
for (i=0; i<(int)vimg->getStrokeCount(); i++)
if(m_sel->isSelected(i))
{
assert(refStroke==-1 || i==0 || m_sel->isSelected(i-1));
if (refStroke==-1)
refStroke = i;
count++;
}
if(count==0) return;
*/
int doMoveGroup(UCHAR moveType, TVectorImage *vimg, const std::vector<std::pair<TStroke *, int>> &selectedGroups, int index)
{
int refStroke = vimg->getStrokeIndex(selectedGroups[index].first);
int count = selectedGroups[index].second;
int moveBefore;
switch (moveType) {
case TGroupCommand::FRONT:
moveBefore = vimg->getStrokeCount();
while (moveBefore >= refStroke + count + 1 && !vimg->canMoveStrokes(refStroke, count, moveBefore))
moveBefore--;
if (moveBefore == refStroke + count)
return -1;
break;
case TGroupCommand::FORWARD:
moveBefore = refStroke + count + 1;
while (moveBefore <= (int)vimg->getStrokeCount() && !vimg->canMoveStrokes(refStroke, count, moveBefore))
moveBefore++;
if (moveBefore == vimg->getStrokeCount() + 1)
return -1;
break;
case TGroupCommand::BACK:
moveBefore = 0;
while (moveBefore <= refStroke - 1 && !vimg->canMoveStrokes(refStroke, count, moveBefore))
moveBefore++;
if (moveBefore == refStroke)
return -1;
break;
case TGroupCommand::BACKWARD:
moveBefore = refStroke - 1;
while (moveBefore >= 0 && !vimg->canMoveStrokes(refStroke, count, moveBefore))
moveBefore--;
if (moveBefore == -1)
return -1;
break;
default:
assert(false);
}
vimg->moveStrokes(refStroke, count, moveBefore);
TXshSimpleLevel *level = TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
TUndoManager::manager()->add(new MoveGroupUndo(level, tool->getCurrentFid(), moveType,
refStroke, count, moveBefore, selectedGroups));
return moveBefore;
}
//-----------------------------------------------------------------------------
void TGroupCommand::moveGroup(UCHAR moveType)
{
TTool *tool = TTool::getApplication()->getCurrentTool()->getTool();
if (!tool)
return;
TVectorImage *vimg = (TVectorImage *)tool->getImage(true);
if (!vimg)
return;
std::vector<std::pair<TStroke *, int>> selectedGroups = getSelectedGroups(vimg, m_sel);
if (selectedGroups.empty())
return;
int i, j;
TUndoManager::manager()->beginBlock();
switch (moveType) {
case TGroupCommand::FRONT:
case TGroupCommand::BACKWARD:
i = 0;
if (moveType == TGroupCommand::BACKWARD && vimg->getStrokeIndex(selectedGroups[i].first) == 0) //tutti i gruppi adiacenti gia in fondo non possono essere backwardati
{
i++;
while (i < (int)selectedGroups.size() &&
vimg->getStrokeIndex(selectedGroups[i - 1].first) + selectedGroups[i - 1].second - 1 == vimg->getStrokeIndex(selectedGroups[i].first) - 1)
i++;
}
for (; i <= (int)selectedGroups.size() - 1; i++)
doMoveGroup(moveType, vimg, selectedGroups, i); //vimg->getStrokeIndex(selectedGroups[i].first), selectedGroups[i].second);
break;
case TGroupCommand::BACK:
case TGroupCommand::FORWARD:
i = selectedGroups.size() - 1;
if (moveType == TGroupCommand::FORWARD && vimg->getStrokeIndex(selectedGroups[i].first) + selectedGroups[i].second - 1 == vimg->getStrokeCount() - 1) //tutti i gruppi adiacenti gia in cime non possono essere forwardati
{
i--;
while (i >= 0 &&
vimg->getStrokeIndex(selectedGroups[i + 1].first) - 1 == vimg->getStrokeIndex(selectedGroups[i].first) + selectedGroups[i].second - 1)
i--;
}
for (; i >= 0; i--)
doMoveGroup(moveType, vimg, selectedGroups, i);
break;
default:
assert(false);
}
TUndoManager::manager()->endBlock();
m_sel->selectNone();
for (i = 0; i < (int)selectedGroups.size(); i++) {
int index = vimg->getStrokeIndex(selectedGroups[i].first);
for (j = index; j < index + selectedGroups[i].second; j++)
m_sel->select(j, true);
}
tool->notifyImageChanged();
}
//-----------------------------------------------------------------------------
void TGroupCommand::addMenuItems(QMenu *menu)
{
UCHAR optionMask = getGroupingOptions();
if (optionMask == 0)
return;
CommandManager *cm = CommandManager::instance();
if (optionMask & TGroupCommand::GROUP)
menu->addAction(cm->getAction(MI_Group));
if (optionMask & TGroupCommand::UNGROUP)
menu->addAction(cm->getAction(MI_Ungroup));
if (optionMask & (TGroupCommand::GROUP | TGroupCommand::UNGROUP) &&
optionMask & (TGroupCommand::FORWARD | TGroupCommand::BACKWARD))
menu->addSeparator();
if (optionMask & TGroupCommand::FORWARD) {
menu->addAction(cm->getAction(MI_BringToFront));
menu->addAction(cm->getAction(MI_BringForward));
}
if (optionMask & TGroupCommand::BACKWARD) {
menu->addAction(cm->getAction(MI_SendBack));
menu->addAction(cm->getAction(MI_SendBackward));
}
menu->addSeparator();
}