#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 //============================================================================= 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 final : public ToolUtils::TToolUndo { std::auto_ptr m_selection; public: GroupUndo(TXshSimpleLevel *level, const TFrameId &frameId, StrokeSelection *selection) : ToolUtils::TToolUndo(level, frameId), m_selection(selection) {} void undo() const override { TVectorImageP image = m_level->getFrame(m_frameId, true); if (image) ungroupWithoutUndo(image.getPointer(), m_selection.get()); } void redo() const override { TVectorImageP image = m_level->getFrame(m_frameId, true); if (image) groupWithoutUndo(image.getPointer(), m_selection.get()); } int getSize() const override { return sizeof(*this); } QString getToolName() override { return QObject::tr("Group"); } }; //============================================================================= // UngroupUndo //----------------------------------------------------------------------------- class UngroupUndo final : public ToolUtils::TToolUndo { std::auto_ptr m_selection; public: UngroupUndo(TXshSimpleLevel *level, const TFrameId &frameId, StrokeSelection *selection) : ToolUtils::TToolUndo(level, frameId), m_selection(selection) {} void undo() const override { TVectorImageP image = m_level->getFrame(m_frameId, true); if (image) groupWithoutUndo(image.getPointer(), m_selection.get()); } void redo() const override { TVectorImageP image = m_level->getFrame(m_frameId, true); if (image) ungroupWithoutUndo(image.getPointer(), m_selection.get()); } int getSize() const override { return sizeof(*this); } QString getToolName() override { return QObject::tr("Ungroup"); } }; //============================================================================= // MoveGroupUndo //----------------------------------------------------------------------------- class MoveGroupUndo final : public ToolUtils::TToolUndo { UCHAR m_moveType; int m_refStroke, m_count, m_moveBefore; std::vector> m_selectedGroups; public: MoveGroupUndo(TXshSimpleLevel *level, const TFrameId &frameId, UCHAR moveType, int refStroke, int count, int moveBefore, const std::vector> &selectedGroups) : ToolUtils::TToolUndo(level, frameId) , m_moveType(moveType) , m_refStroke(refStroke) , m_count(count) , m_moveBefore(moveBefore) , m_selectedGroups(selectedGroups) {} ~MoveGroupUndo() {} void undo() const override { 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( 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 override { 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( 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 override { return sizeof(*this); } QString getToolName() override { return QObject::tr("Move Group"); } }; //----------------------------------------------------------------------------- } // namespace //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // TGroupCommand //----------------------------------------------------------------------------- namespace { std::vector> getSelectedGroups(TVectorImage *vimg, StrokeSelection *sel) { UINT i, j; std::vector> 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>(); ret.push_back(std::pair(vimg->getStroke(i), j - i)); i = j - 1; } else ret.push_back(std::pair(vimg->getStroke(i), 1)); } return ret; } } // namepsace //-------------------------------------------------------------------------------------- UCHAR TGroupCommand::getGroupingOptions() { TTool *tool = TTool::getApplication()->getCurrentTool()->getTool(); if (!tool) return 0; TVectorImage *vimg = dynamic_cast(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> 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; igetStrokeCount(); 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; jgetStrokeCount(); j++) if (!m_sel->isSelected(j) && vimg->sameSubGroup(i, j)) break;//non tutto il gruppo e' selezionato if (jgetStrokeCount()) 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(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> 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(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)getStrokeCount(); i++) if (vimg->getCommonGroupDepth(fromIndex, i)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> &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> 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(); }