| |
| |
| #include "tools/imagegrouping.h" |
| |
| |
| #include "tools/strokeselection.h" |
| #include "tools/tool.h" |
| #include "tools/toolhandle.h" |
| #include "tools/toolutils.h" |
| #include "vectorselectiontool.h" |
| |
| |
| #include "toonzqt/tselectionhandle.h" |
| #include "toonzqt/selectioncommandids.h" |
| |
| |
| #include "toonz/tscenehandle.h" |
| #include "toonz/txshlevelhandle.h" |
| |
| |
| #include "tvectorimage.h" |
| #include "tundo.h" |
| #include "tthreadmessage.h" |
| |
| |
| #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) |
| |
| |
| { |
| 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(); |
| } |
| |
| |
| |
| |
| |
| class GroupUndo final : public ToolUtils::TToolUndo { |
| std::unique_ptr<StrokeSelection> 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"); } |
| }; |
| |
| |
| |
| |
| |
| class UngroupUndo final : public ToolUtils::TToolUndo { |
| std::unique_ptr<StrokeSelection> 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"); } |
| }; |
| |
| |
| |
| |
| |
| class MoveGroupUndo final : 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 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<StrokeSelection *>( |
| TTool::getApplication()->getCurrentSelection()->getSelection()); |
| if (selection) { |
| |
| 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<StrokeSelection *>( |
| TTool::getApplication()->getCurrentSelection()->getSelection()); |
| if (selection) { |
| |
| 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 { |
| |
| 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; |
| } |
| |
| } |
| |
| |
| |
| 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; |
| |
| |
| |
| |
| std::vector<std::pair<TStroke *, int>> strokeIndexes = |
| getSelectedGroups(vimg, m_sel); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (strokeIndexes.empty()) return 0; |
| |
| 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; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| |
| 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; |
| |
| |
| 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; |
| } |
| groups.push_back(pair<int, int>(strokeIndex, count)); |
| |
| i = i + count; |
| } |
| |
| if (groups.empty()) return 0; |
| |
| if (!valid) return 0; |
| |
| if (strokeIndex + count < (int)vimg->getStrokeCount()) { |
| mask |= FRONT; |
| mask |= FORWARD; |
| } |
| if (strokeIndex > 0) { |
| mask |= BACK; |
| mask |= BACKWARD; |
| } |
| |
| |
| |
| |
| 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; |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| |
| 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))); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static 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) |
| |
| { |
| 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); |
| |
| 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) |
| |
| { |
| 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(); |
| } |
| |