diff --git a/libs/ui/flake/kis_node_dummies_graph.cpp b/libs/ui/flake/kis_node_dummies_graph.cpp index d4ec0dc8a4..7b49384783 100644 --- a/libs/ui/flake/kis_node_dummies_graph.cpp +++ b/libs/ui/flake/kis_node_dummies_graph.cpp @@ -1,192 +1,205 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_dummies_graph.h" #include "kis_node_shape.h" +#include "kis_selection_mask.h" /********************************************************************/ /* KisNodeDummy */ /********************************************************************/ KisNodeDummy::KisNodeDummy(KisNodeShape *nodeShape, KisNodeSP node) : m_nodeShape(nodeShape), m_node(node) { } KisNodeDummy::~KisNodeDummy() { qDeleteAll(m_children); } KisNodeDummy* KisNodeDummy::firstChild() const { return !m_children.isEmpty() ? m_children.first() : 0; } KisNodeDummy* KisNodeDummy::lastChild() const { return !m_children.isEmpty() ? m_children.last() : 0; } KisNodeDummy* KisNodeDummy::nextSibling() const { if(!parent()) return 0; int index = parent()->m_children.indexOf(const_cast(this)); Q_ASSERT(index >= 0); index++; return index < parent()->m_children.size() ? parent()->m_children[index] : 0; } KisNodeDummy* KisNodeDummy::prevSibling() const { if(!parent()) return 0; int index = parent()->m_children.indexOf(const_cast(this)); Q_ASSERT(index >= 0); index--; return index >= 0 ? parent()->m_children[index] : 0; } KisNodeDummy* KisNodeDummy::parent() const { return qobject_cast(QObject::parent()); } KisNodeShape* KisNodeDummy::nodeShape() const { return m_nodeShape; } KisNodeSP KisNodeDummy::node() const { return m_node; } +bool KisNodeDummy::isGUIVisible(bool showGlobalSelection) const +{ + if (!showGlobalSelection && + parent() && !parent()->parent() && + dynamic_cast(m_node.data())) { + + return false; + } + + return parent() && !m_node->isFakeNode(); +} + KisNodeDummy* KisNodeDummy::at(int index) const { return m_children.at(index); } int KisNodeDummy::childCount() const { return m_children.size(); } int KisNodeDummy::indexOf(KisNodeDummy *child) const { return m_children.indexOf(child); } /********************************************************************/ /* KisNodeDummiesGraph */ /********************************************************************/ KisNodeDummiesGraph::KisNodeDummiesGraph() : m_rootDummy(0) { } KisNodeDummy* KisNodeDummiesGraph::rootDummy() const { return m_rootDummy; } void KisNodeDummiesGraph::addNode(KisNodeDummy *node, KisNodeDummy *parent, KisNodeDummy *aboveThis) { Q_ASSERT(!containsNode(node->node())); node->setParent(parent); Q_ASSERT_X(parent || !m_rootDummy, "KisNodeDummiesGraph::addNode", "Trying to add second root dummy"); Q_ASSERT_X(!parent || m_rootDummy, "KisNodeDummiesGraph::addNode", "Trying to add non-orphan child with no root dummy set"); if(!parent) { m_rootDummy = node; } else { int insertionIndex = parent->m_children.size(); insertionIndex = aboveThis ? parent->m_children.indexOf(aboveThis) + 1: 0; Q_ASSERT(!aboveThis || parent->m_children.indexOf(aboveThis) >= 0); parent->m_children.insert(insertionIndex, node); } m_dummiesMap[node->node()] = node; } void KisNodeDummiesGraph::removeNode(KisNodeDummy *node) { Q_ASSERT(containsNode(node->node())); unmapDummyRecursively(node); KisNodeDummy *parent = node->parent(); Q_ASSERT_X(m_rootDummy, "KisNodeDummiesGraph::removeNode", "Trying to remove a dummy with no root dummy"); if(!parent) { m_rootDummy = 0; } else { parent->m_children.removeOne(node); } } void KisNodeDummiesGraph::unmapDummyRecursively(KisNodeDummy *dummy) { m_dummiesMap.remove(dummy->node()); KisNodeDummy *child = dummy->firstChild(); while(child) { unmapDummyRecursively(child); child = child->nextSibling(); } } void KisNodeDummiesGraph::moveNode(KisNodeDummy *node, KisNodeDummy *parent, KisNodeDummy *aboveThis) { removeNode(node); addNode(node, parent, aboveThis); } bool KisNodeDummiesGraph::containsNode(KisNodeSP node) const { return m_dummiesMap.contains(node); } int KisNodeDummiesGraph::dummiesCount() const { return m_dummiesMap.size(); } KisNodeDummy* KisNodeDummiesGraph::nodeToDummy(KisNodeSP node) { if (!m_dummiesMap.contains(node)) { return 0; } return m_dummiesMap[node]; } diff --git a/libs/ui/flake/kis_node_dummies_graph.h b/libs/ui/flake/kis_node_dummies_graph.h index 4c91dcc567..4421b8201e 100644 --- a/libs/ui/flake/kis_node_dummies_graph.h +++ b/libs/ui/flake/kis_node_dummies_graph.h @@ -1,146 +1,148 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_NODE_DUMMIES_GRAPH_H #define __KIS_NODE_DUMMIES_GRAPH_H #include #include #include "kritaui_export.h" #include "kis_types.h" #include "kis_node.h" class KisNodeShape; /** * KisNodeDummy is a simplified representation of a node * in the node stack. It stores all the hierarchy information * about the node, so you needn't access from the node * directly (actually, you cannot do it usually, because UI * works in a different thread and race conditions are possible). * * The dummy stores a KisNodeShape which can store a pointer to * to node. You can access to the node data through it, but take * care -- not all the information is accessible in * multithreading environment. * * The ownership on the KisNodeShape is taken by the dummy. * The ownership on the children of the dummy is taken as well. */ class KRITAUI_EXPORT KisNodeDummy : public QObject { Q_OBJECT public: /** * Take care tha KisNodeDummy does not take ownership over * the \p nodeShape since the handling of the removal of the * children of the shape is done by flake. So please handle it * manually. * * The children dummies of the dummy are still owned by the * dummy and are deleted automatically. */ KisNodeDummy(KisNodeShape *nodeShape, KisNodeSP node); ~KisNodeDummy() override; KisNodeDummy* firstChild() const; KisNodeDummy* lastChild() const; KisNodeDummy* nextSibling() const; KisNodeDummy* prevSibling() const; KisNodeDummy* parent() const; KisNodeDummy* at(int index) const; int childCount() const; int indexOf(KisNodeDummy *child) const; KisNodeSP node() const; + bool isGUIVisible(bool showGlobalSelection) const; + private: friend class KisNodeShapesGraph; // for ::nodeShape() method friend class KisNodeShapesGraphTest; KisNodeShape* nodeShape() const; friend class KisNodeDummiesGraph; QList m_children; KisNodeShape *m_nodeShape; KisNodeSP m_node; }; /** * KisNodeDummiesGraph manages the hierarchy of dummy objects * representing nodes in the UI environment. */ class KRITAUI_EXPORT KisNodeDummiesGraph { public: KisNodeDummiesGraph(); KisNodeDummy* rootDummy() const; KisNodeDummy* nodeToDummy(KisNodeSP node); bool containsNode(KisNodeSP node) const; int dummiesCount() const; /** * Adds a dummy \p node to the position specified * by \p parent and \p aboveThis. * * It is not expected that you would add a dummy twice. */ void addNode(KisNodeDummy *node, KisNodeDummy *parent, KisNodeDummy *aboveThis); /** * Moves a dummy \p node from its current position to * the position specified by \p parent and \p aboveThis. * * It is expected that the dummy \p node has been added * to the graph with addNode() before calling this function. */ void moveNode(KisNodeDummy *node, KisNodeDummy *parent, KisNodeDummy *aboveThis); /** * Removes the dummy \p node from the graph. * * WARNING: The dummy is only "unlinked" from the graph. Neither * deletion of the node nor deletion of its children happens. * The dummy keeps maintaining its children so after unlinking * it from the graph you can just type to free memory recursively: * \code * graph.removeNode(node); * delete node; * \endcode */ void removeNode(KisNodeDummy *node); private: void unmapDummyRecursively(KisNodeDummy *dummy); private: typedef QMap NodeMap; private: KisNodeDummy *m_rootDummy; NodeMap m_dummiesMap; }; #endif /* __KIS_NODE_DUMMIES_GRAPH_H */ diff --git a/libs/ui/kis_model_index_converter.cpp b/libs/ui/kis_model_index_converter.cpp index cc347d64aa..0970f10ff6 100644 --- a/libs/ui/kis_model_index_converter.cpp +++ b/libs/ui/kis_model_index_converter.cpp @@ -1,192 +1,192 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_model_index_converter.h" #include "kis_selection_mask.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "kis_node_model.h" #include "kis_node_manager.h" #include "KisReferenceImagesLayer.h" KisModelIndexConverter::KisModelIndexConverter(KisDummiesFacadeBase *dummiesFacade, KisNodeModel *model, bool showGlobalSelection) : m_dummiesFacade(dummiesFacade), m_model(model), m_showGlobalSelection(showGlobalSelection) { } inline bool KisModelIndexConverter::checkDummyType(KisNodeDummy *dummy) { - return !KisNodeManager::isNodeHidden(dummy->node(), !m_showGlobalSelection); + return dummy->isGUIVisible(m_showGlobalSelection); } inline bool KisModelIndexConverter::checkDummyMetaObjectType(const QString &type) { const QString selectionMaskType = KisSelectionMask::staticMetaObject.className(); const QString referencesLayerType = KisReferenceImagesLayer::staticMetaObject.className(); return (type != selectionMaskType || m_showGlobalSelection) && type != referencesLayerType; } KisNodeDummy* KisModelIndexConverter::dummyFromRow(int row, QModelIndex parent) { KisNodeDummy *parentDummy = parent.isValid() ? dummyFromIndex(parent) : m_dummiesFacade->rootDummy(); if(!parentDummy) return 0; KisNodeDummy *resultDummy = 0; // a child of the root node if(!parentDummy->parent()) { KisNodeDummy *currentDummy = parentDummy->lastChild(); while(currentDummy) { if(checkDummyType(currentDummy)) { if(!row) { resultDummy = currentDummy; break; } row--; } currentDummy = currentDummy->prevSibling(); } } // a child of other layer else { int rowCount = parentDummy->childCount(); int index = rowCount - row - 1; resultDummy = parentDummy->at(index); } return resultDummy; } KisNodeDummy* KisModelIndexConverter::dummyFromIndex(QModelIndex index) { Q_ASSERT(index.isValid()); Q_ASSERT(index.internalPointer()); return static_cast(index.internalPointer()); } QModelIndex KisModelIndexConverter::indexFromDummy(KisNodeDummy *dummy) { Q_ASSERT(dummy); KisNodeDummy *parentDummy = dummy->parent(); // a root node if(!parentDummy) return QModelIndex(); int row = 0; // a child of the root node if(!parentDummy->parent()) { if(!checkDummyType(dummy)) return QModelIndex(); KisNodeDummy *currentDummy = parentDummy->lastChild(); while(currentDummy && currentDummy != dummy) { if(checkDummyType(currentDummy)) { row++; } currentDummy = currentDummy->prevSibling(); } } // a child of other layer else { int rowCount = parentDummy->childCount(); int index = parentDummy->indexOf(dummy); row = rowCount - index - 1; } return m_model->createIndex(row, 0, (void*)dummy); } bool KisModelIndexConverter::indexFromAddedDummy(KisNodeDummy *parentDummy, int index, const QString &newNodeMetaObjectType, QModelIndex &parentIndex, int &row) { // adding a root node if(!parentDummy) { Q_ASSERT(!index); return false; } // adding a child of the root node if(!parentDummy->parent()) { if(!checkDummyMetaObjectType(newNodeMetaObjectType)) { return false; } row = 0; parentIndex = QModelIndex(); KisNodeDummy *dummy = parentDummy->lastChild(); int toScan = parentDummy->childCount() - index; while(dummy && toScan > 0) { if(checkDummyType(dummy)) { row++; } dummy = dummy->prevSibling(); toScan--; } } // everything else else { parentIndex = indexFromDummy(parentDummy); int rowCount = parentDummy->childCount(); row = rowCount - index; } return true; } int KisModelIndexConverter::rowCount(QModelIndex parent) { KisNodeDummy *dummy = parent.isValid() ? dummyFromIndex(parent) : m_dummiesFacade->rootDummy(); // a root node (hidden) if(!dummy) return 0; int numChildren = 0; // children of the root node if(!dummy->parent()) { KisNodeDummy *currentDummy = dummy->lastChild(); while(currentDummy) { if(checkDummyType(currentDummy)) { numChildren++; } currentDummy = currentDummy->prevSibling(); } } // children of other nodes else { numChildren = dummy->childCount(); } return numChildren; } diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp index e0e418c779..17fef5c32f 100644 --- a/libs/ui/kis_node_manager.cpp +++ b/libs/ui/kis_node_manager.cpp @@ -1,1583 +1,1583 @@ /* * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPart.h" #include "canvas/kis_canvas2.h" #include "kis_shape_controller.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_mask_manager.h" #include "kis_group_layer.h" #include "kis_layer_manager.h" #include "kis_selection_manager.h" #include "kis_node_commands_adapter.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_processing_applicator.h" #include "kis_sequential_iterator.h" #include "kis_transaction.h" #include "kis_node_selection_adapter.h" #include "kis_node_insertion_adapter.h" #include "kis_node_juggler_compressed.h" #include "KisNodeDisplayModeAdapter.h" #include "kis_clipboard.h" #include "kis_node_dummies_graph.h" #include "kis_mimedata.h" #include "kis_layer_utils.h" #include "krita_utils.h" #include "kis_shape_layer.h" #include "processing/kis_mirror_processing_visitor.h" #include "KisView.h" #include #include #include struct KisNodeManager::Private { Private(KisNodeManager *_q, KisViewManager *v) : q(_q) , view(v) , imageView(0) , layerManager(v) , maskManager(v) , commandsAdapter(v) , nodeSelectionAdapter(new KisNodeSelectionAdapter(q)) , nodeInsertionAdapter(new KisNodeInsertionAdapter(q)) , nodeDisplayModeAdapter(new KisNodeDisplayModeAdapter()) , lastRequestedIsolatedModeStatus(false) { } KisNodeManager * q; KisViewManager * view; QPointerimageView; KisLayerManager layerManager; KisMaskManager maskManager; KisNodeCommandsAdapter commandsAdapter; QScopedPointer nodeSelectionAdapter; QScopedPointer nodeInsertionAdapter; QScopedPointer nodeDisplayModeAdapter; KisAction *showInTimeline; KisNodeList selectedNodes; QPointer nodeJuggler; KisNodeWSP previouslyActiveNode; bool activateNodeImpl(KisNodeSP node); QSignalMapper nodeCreationSignalMapper; QSignalMapper nodeConversionSignalMapper; bool lastRequestedIsolatedModeStatus; void saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity); void mergeTransparencyMaskAsAlpha(bool writeToLayers); KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName); }; bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node) { Q_ASSERT(view); Q_ASSERT(view->canvasBase()); Q_ASSERT(view->canvasBase()->globalShapeManager()); Q_ASSERT(imageView); if (node && node == q->activeNode()) { return false; } // Set the selection on the shape manager to the active layer // and set call KoSelection::setActiveLayer( KoShapeLayer* layer ) // with the parent of the active layer. KoSelection *selection = view->canvasBase()->globalShapeManager()->selection(); Q_ASSERT(selection); selection->deselectAll(); if (!node) { selection->setActiveLayer(0); imageView->setCurrentNode(0); maskManager.activateMask(0); layerManager.activateLayer(0); previouslyActiveNode = q->activeNode(); } else { previouslyActiveNode = q->activeNode(); KoShape * shape = view->document()->shapeForNode(node); //if (!shape) return false; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, false); selection->select(shape); KoShapeLayer * shapeLayer = dynamic_cast(shape); //if (!shapeLayer) return false; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false); // shapeLayer->setGeometryProtected(node->userLocked()); // shapeLayer->setVisible(node->visible()); selection->setActiveLayer(shapeLayer); imageView->setCurrentNode(node); if (KisLayerSP layer = qobject_cast(node.data())) { maskManager.activateMask(0); layerManager.activateLayer(layer); } else if (KisMaskSP mask = dynamic_cast(node.data())) { maskManager.activateMask(mask); // XXX_NODE: for now, masks cannot be nested. layerManager.activateLayer(static_cast(node->parent().data())); } } return true; } KisNodeManager::KisNodeManager(KisViewManager *view) : m_d(new Private(this, view)) { connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP))); } KisNodeManager::~KisNodeManager() { delete m_d; } void KisNodeManager::setView(QPointerimageView) { m_d->maskManager.setView(imageView); m_d->layerManager.setView(imageView); if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this); m_d->imageView->image()->disconnect(this); } m_d->imageView = imageView; if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP))); connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeActionImageStatusChange())); connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP,KisNodeList)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP,KisNodeList))); m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode()); } } #define NEW_LAYER_ACTION(id, layerType) \ { \ action = actionManager->createAction(id); \ m_d->nodeCreationSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeCreationSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION_2(id, layerType, exclude) \ { \ action = actionManager->createAction(id); \ action->setExcludedNodeTypes(QStringList(exclude)); \ actionManager->addAction(id, action); \ m_d->nodeConversionSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeConversionSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION(id, layerType) \ CONVERT_NODE_ACTION_2(id, layerType, layerType) void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager) { m_d->layerManager.setup(actionManager); m_d->maskManager.setup(actionCollection, actionManager); KisAction * action = 0; action = actionManager->createAction("mirrorNodeX"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX())); action = actionManager->createAction("mirrorNodeY"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY())); action = actionManager->createAction("mirrorAllNodesX"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesX())); action = actionManager->createAction("mirrorAllNodesY"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesY())); action = actionManager->createAction("activateNextLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode())); action = actionManager->createAction("activatePreviousLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode())); action = actionManager->createAction("switchToPreviouslyActiveNode"); connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode())); action = actionManager->createAction("save_node_as_image"); connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage())); action = actionManager->createAction("save_vector_node_to_svg"); connect(action, SIGNAL(triggered()), this, SLOT(saveVectorLayerAsImage())); action->setActivationFlags(KisAction::ACTIVE_SHAPE_LAYER); action = actionManager->createAction("duplicatelayer"); connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode())); action = actionManager->createAction("copy_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard())); action = actionManager->createAction("cut_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard())); action = actionManager->createAction("paste_layer_from_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard())); action = actionManager->createAction("create_quick_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup())); action = actionManager->createAction("create_quick_clipping_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup())); action = actionManager->createAction("quick_ungroup"); connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup())); action = actionManager->createAction("select_all_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes())); action = actionManager->createAction("select_visible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes())); action = actionManager->createAction("select_locked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes())); action = actionManager->createAction("select_invisible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes())); action = actionManager->createAction("select_unlocked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes())); action = actionManager->createAction("new_from_visible"); connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible())); action = actionManager->createAction("show_in_timeline"); action->setCheckable(true); connect(action, SIGNAL(toggled(bool)), this, SLOT(slotShowHideTimeline(bool))); m_d->showInTimeline = action; NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer"); NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer"); NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer"); NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer"); NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer"); NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer"); NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer"); NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask"); NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask"); NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask"); NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask"); NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask"); connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(QString)), this, SLOT(createNode(QString))); CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer"); CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask"); CONVERT_NODE_ACTION("convert_to_animated", "animated"); CONVERT_NODE_ACTION_2("convert_to_file_layer", "KisFileLayer", QStringList() << "KisFileLayer" << "KisCloneLayer"); connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(QString)), this, SLOT(convertNode(QString))); action = actionManager->createAction("isolate_layer"); connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool))); action = actionManager->createAction("toggle_layer_visibility"); connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility())); action = actionManager->createAction("toggle_layer_lock"); connect(action, SIGNAL(triggered()), this, SLOT(toggleLock())); action = actionManager->createAction("toggle_layer_inherit_alpha"); connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha())); action = actionManager->createAction("toggle_layer_alpha_lock"); connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock())); action = actionManager->createAction("split_alpha_into_mask"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask())); action = actionManager->createAction("split_alpha_write"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite())); // HINT: we can save even when the nodes are not editable action = actionManager->createAction("split_alpha_save_merged"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryRestartIsolatedMode())); } void KisNodeManager::updateGUI() { // enable/disable all relevant actions m_d->layerManager.updateGUI(); m_d->maskManager.updateGUI(); } KisNodeSP KisNodeManager::activeNode() { if (m_d->imageView) { return m_d->imageView->currentNode(); } return 0; } KisLayerSP KisNodeManager::activeLayer() { return m_d->layerManager.activeLayer(); } const KoColorSpace* KisNodeManager::activeColorSpace() { if (m_d->maskManager.activeDevice()) { return m_d->maskManager.activeDevice()->colorSpace(); } else { Q_ASSERT(m_d->layerManager.activeLayer()); if (m_d->layerManager.activeLayer()->parentLayer()) return m_d->layerManager.activeLayer()->parentLayer()->colorSpace(); else return m_d->view->image()->colorSpace(); } } void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index) { if (parent->allowAsChild(node)) { if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) { KisSelectionMask *m = dynamic_cast(node.data()); KisLayer *l = qobject_cast(parent.data()); if (m && m->active() && l && l->selectionMask()) { l->selectionMask()->setActive(false); } } m_d->commandsAdapter.moveNode(node, parent, index); } } void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Move Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, aboveThis); } void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Copy Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->copyNode(nodes, parent, aboveThis); } void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Add Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->addNode(nodes, parent, aboveThis); } void KisNodeManager::toggleIsolateActiveNode() { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode == image->isolatedModeRoot()) { toggleIsolateMode(false); } else { toggleIsolateMode(true); } } void KisNodeManager::toggleIsolateMode(bool checked) { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); if (checked && activeNode) { // Transform and colorize masks don't have pixel data... if (activeNode->inherits("KisTransformMask") || activeNode->inherits("KisColorizeMask")) return; if (!image->startIsolatedMode(activeNode)) { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); action->setChecked(false); } } else { image->stopIsolatedMode(); } m_d->lastRequestedIsolatedModeStatus = checked; } void KisNodeManager::slotUpdateIsolateModeActionImageStatusChange() { slotUpdateIsolateModeAction(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (this->activeNode() && bool(isolatedRootNode) != m_d->lastRequestedIsolatedModeStatus) { slotTryRestartIsolatedMode(); } } void KisNodeManager::slotUpdateIsolateModeAction() { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); Q_ASSERT(action); KisNodeSP activeNode = this->activeNode(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); action->setChecked(isolatedRootNode && isolatedRootNode == activeNode); } void KisNodeManager::slotTryRestartIsolatedMode() { /** * It might be that we have multiple Krita windows open. In such a case * only the currently active one should restart isolated mode */ if (!m_d->view->mainWindow()->isActiveWindow()) return; KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (!isolatedRootNode && !m_d->lastRequestedIsolatedModeStatus) return; this->toggleIsolateMode(true); } KisNodeSP KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom) { if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) { return 0; } KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } KIS_ASSERT_RECOVER_RETURN_VALUE(activeNode, 0); // XXX: make factories for this kind of stuff, // with a registry if (nodeType == "KisPaintLayer") { return m_d->layerManager.addPaintLayer(activeNode); } else if (nodeType == "KisGroupLayer") { return m_d->layerManager.addGroupLayer(activeNode); } else if (nodeType == "KisAdjustmentLayer") { return m_d->layerManager.addAdjustmentLayer(activeNode); } else if (nodeType == "KisGeneratorLayer") { return m_d->layerManager.addGeneratorLayer(activeNode); } else if (nodeType == "KisShapeLayer") { return m_d->layerManager.addShapeLayer(activeNode); } else if (nodeType == "KisCloneLayer") { KisNodeList nodes = selectedNodes(); if (nodes.isEmpty()) { nodes.append(activeNode); } return m_d->layerManager.addCloneLayer(nodes); } else if (nodeType == "KisTransparencyMask") { return m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false); } else if (nodeType == "KisFilterMask") { return m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false); } else if (nodeType == "KisColorizeMask") { return m_d->maskManager.createColorizeMask(activeNode); } else if (nodeType == "KisTransformMask") { return m_d->maskManager.createTransformMask(activeNode); } else if (nodeType == "KisSelectionMask") { return m_d->maskManager.createSelectionMask(activeNode, copyFrom, false); } else if (nodeType == "KisFileLayer") { return m_d->layerManager.addFileLayer(activeNode); } return 0; } void KisNodeManager::createFromVisible() { KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild()); } void KisNodeManager::slotShowHideTimeline(bool value) { Q_FOREACH (KisNodeSP node, selectedNodes()) { node->setUseInTimeline(value); } } KisLayerSP KisNodeManager::createPaintLayer() { KisNodeSP node = createNode("KisPaintLayer"); return dynamic_cast(node.data()); } void KisNodeManager::convertNode(const QString &nodeType) { if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) { return; } KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; if (nodeType == "KisPaintLayer") { m_d->layerManager.convertNodeToPaintLayer(activeNode); } else if (nodeType == "KisSelectionMask" || nodeType == "KisFilterMask" || nodeType == "KisTransparencyMask") { KisPaintDeviceSP copyFrom = activeNode->paintDevice() ? activeNode->paintDevice() : activeNode->projection(); m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask")); bool result = false; if (nodeType == "KisSelectionMask") { result = !m_d->maskManager.createSelectionMask(activeNode, copyFrom, true).isNull(); } else if (nodeType == "KisFilterMask") { result = !m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true).isNull(); } else if (nodeType == "KisTransparencyMask") { result = !m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true).isNull(); } m_d->commandsAdapter.endMacro(); if (!result) { m_d->view->blockUntilOperationsFinishedForced(m_d->imageView->image()); m_d->commandsAdapter.undoLastCommand(); } } else if (nodeType == "KisFileLayer") { m_d->layerManager.convertLayerToFileLayer(activeNode); } else { warnKrita << "Unsupported node conversion type:" << nodeType; } } void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node) { KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); KIS_SAFE_ASSERT_RECOVER_RETURN(dummiesFacade); const bool nodeVisible = !isNodeHidden(node, !m_d->nodeDisplayModeAdapter->showGlobalSelectionMask()); if (!nodeVisible) { return; } KIS_ASSERT_RECOVER_RETURN(node != activeNode()); if (m_d->activateNodeImpl(node)) { emit sigUiNeedChangeActiveNode(node); emit sigNodeActivated(node); nodesUpdated(); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } } void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node) { // the node must still be in the graph, some asynchronous // signals may easily break this requirement if (node && !node->graphListener()) { node = 0; } if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } void KisNodeManager::slotUiActivatedNode(KisNodeSP node) { // the node must still be in the graph, some asynchronous // signals may easily break this requirement if (node && !node->graphListener()) { node = 0; } if (node) { QStringList vectorTools = QStringList() << "InteractionTool" << "KarbonPatternTool" << "KarbonGradientTool" << "KarbonCalligraphyTool" << "CreateShapesTool" << "PathTool"; QStringList pixelTools = QStringList() << "KritaShape/KisToolBrush" << "KritaShape/KisToolDyna" << "KritaShape/KisToolMultiBrush" << "KritaFill/KisToolFill" << "KritaFill/KisToolGradient"; KisSelectionMask *selectionMask = dynamic_cast(node.data()); const bool nodeHasVectorAbilities = node->inherits("KisShapeLayer") || (selectionMask && selectionMask->selection()->hasShapeSelection()); if (nodeHasVectorAbilities) { if (pixelTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("InteractionTool"); } } else { if (vectorTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } } } if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); } void KisNodeManager::nodesUpdated() { KisNodeSP node = activeNode(); if (!node) return; m_d->layerManager.layersUpdated(); m_d->maskManager.masksUpdated(); m_d->view->updateGUI(); m_d->view->selectionManager()->selectionChanged(); { KisSignalsBlocker b(m_d->showInTimeline); m_d->showInTimeline->setChecked(node->useInTimeline()); } } KisPaintDeviceSP KisNodeManager::activePaintDevice() { return m_d->maskManager.activeMask() ? m_d->maskManager.activeDevice() : m_d->layerManager.activeDevice(); } void KisNodeManager::nodeProperties(KisNodeSP node) { if ((selectedNodes().size() > 1 && node->inherits("KisLayer")) || node->inherits("KisLayer")) { m_d->layerManager.layerProperties(); } else if (node->inherits("KisMask")) { m_d->maskManager.maskProperties(); } } void KisNodeManager::changeCloneSource() { m_d->layerManager.changeCloneSource(); } qint32 KisNodeManager::convertOpacityToInt(qreal opacity) { /** * Scales opacity from the range 0...100 * to the integer range 0...255 */ return qMin(255, int(opacity * 2.55 + 0.5)); } void KisNodeManager::setNodeName(KisNodeSP node, const QString &name) { if (!node) return; if (node->name() == name) return; m_d->commandsAdapter.setNodeName(node, name); } void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity) { if (!node) return; if (node->opacity() == opacity) return; m_d->commandsAdapter.setOpacity(node, opacity); } void KisNodeManager::setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp) { if (!node) return; if (node->compositeOp() == compositeOp) return; m_d->commandsAdapter.setCompositeOp(node, compositeOp); } void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes) { if (activeNode) { slotNonUiActivatedNode(activeNode); } if (!selectedNodes.isEmpty()) { slotSetSelectedNodes(selectedNodes); } } void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes) { m_d->selectedNodes = nodes; emit sigUiNeedChangeSelectedNodes(nodes); } KisNodeList KisNodeManager::selectedNodes() { return m_d->selectedNodes; } KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const { return m_d->nodeSelectionAdapter.data(); } KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const { return m_d->nodeInsertionAdapter.data(); } KisNodeDisplayModeAdapter *KisNodeManager::nodeDisplayModeAdapter() const { return m_d->nodeDisplayModeAdapter.data(); } bool KisNodeManager::isNodeHidden(KisNodeSP node, bool isGlobalSelectionHidden) { if (node && node->isFakeNode()) { return true; } if (isGlobalSelectionHidden && dynamic_cast(node.data()) && (!node->parent() || !node->parent()->parent())) { return true; } return false; } bool KisNodeManager::trySetNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const { const KisPaintLayer *paintLayer = dynamic_cast(node.data()); if (paintLayer) { const auto onionSkinOn = KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::onionSkins, true); if (properties.contains(onionSkinOn)) { const KisPaintDeviceSP &paintDevice = paintLayer->paintDevice(); if (paintDevice && paintDevice->defaultPixel().opacityU8() == 255) { m_d->view->showFloatingMessage(i18n("Onion skins require a layer with transparent background."), QIcon()); return false; } } } KisNodePropertyListCommand::setNodePropertiesNoUndo(node, image, properties); return true; } void KisNodeManager::nodeOpacityChanged(qreal opacity) { KisNodeSP node = activeNode(); setNodeOpacity(node, convertOpacityToInt(opacity)); } void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op) { KisNodeSP node = activeNode(); setNodeCompositeOp(node, op); } void KisNodeManager::duplicateActiveNode() { KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->duplicateNode(selectedNodes()); } KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName) { KisImageWSP image = view->image(); if (!nodeJuggler || (nodeJuggler && (nodeJuggler->isEnded() || !nodeJuggler->canMergeAction(actionName)))) { nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750); nodeJuggler->setAutoDelete(true); } return nodeJuggler; } void KisNodeManager::raiseNode() { KUndo2MagicString actionName = kundo2_i18n("Raise Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->raiseNode(selectedNodes()); } void KisNodeManager::lowerNode() { KUndo2MagicString actionName = kundo2_i18n("Lower Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->lowerNode(selectedNodes()); } void KisNodeManager::removeSingleNode(KisNodeSP node) { if (!node || !node->parent()) { return; } KisNodeList nodes; nodes << node; removeSelectedNodes(nodes); } void KisNodeManager::removeSelectedNodes(KisNodeList nodes) { KUndo2MagicString actionName = kundo2_i18n("Remove Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::removeNode() { removeSelectedNodes(selectedNodes()); } void KisNodeManager::mirrorNodeX() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer X"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask X"); } mirrorNode(node, commandName, Qt::Horizontal, m_d->view->selection()); } void KisNodeManager::mirrorNodeY() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer Y"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask Y"); } mirrorNode(node, commandName, Qt::Vertical, m_d->view->selection()); } void KisNodeManager::mirrorAllNodesX() { KisNodeSP node = m_d->view->image()->root(); mirrorNode(node, kundo2_i18n("Mirror All Layers X"), Qt::Horizontal, m_d->view->selection()); } void KisNodeManager::mirrorAllNodesY() { KisNodeSP node = m_d->view->image()->root(); mirrorNode(node, kundo2_i18n("Mirror All Layers Y"), Qt::Vertical, m_d->view->selection()); } void KisNodeManager::activateNextNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node = activeNode->nextSibling(); while (node && node->childCount() > 0) { node = node->firstChild(); } if (!node && activeNode->parent() && activeNode->parent()->parent()) { node = activeNode->parent(); } - while(node && isNodeHidden(node, true)) { + while(node && isNodeHidden(node, m_d->nodeDisplayModeAdapter->showGlobalSelectionMask())) { node = node->nextSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::activatePreviousNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node; if (activeNode->childCount() > 0) { node = activeNode->lastChild(); } else { node = activeNode->prevSibling(); } while (!node && activeNode->parent()) { node = activeNode->parent()->prevSibling(); activeNode = activeNode->parent(); } - while(node && isNodeHidden(node, true)) { + while(node && isNodeHidden(node, m_d->nodeDisplayModeAdapter->showGlobalSelectionMask())) { node = node->prevSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::switchToPreviouslyActiveNode() { if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) { slotNonUiActivatedNode(m_d->previouslyActiveNode); } } void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation, KisSelectionSP selection) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(m_d->view->image(), node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor; if (selection) { visitor = new KisMirrorProcessingVisitor(selection, orientation); } else { visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation); } if (!selection) { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } applicator.end(); nodesUpdated(); } void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity) { KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18n("Export \"%1\"", defaultName)); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export)); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename, false); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.width(), bounds.height(), device->compositionSourceColorSpace(), defaultName); dst->setResolution(xRes, yRes); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity); paintLayer->paintDevice()->makeCloneFrom(device, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); if (!doc->exportDocumentSync(url, mimefilter.toLatin1())) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Could not save the layer. %1", doc->errorMessage().toUtf8().data()), QMessageBox::Ok); } } void KisNodeManager::saveNodeAsImage() { KisNodeSP node = activeNode(); if (!node) { warnKrita << "BUG: Save Node As Image was called without any node selected"; return; } KisImageWSP image = m_d->view->image(); QRect saveRect = image->bounds() | node->exactBounds(); m_d->saveDeviceAsImage(node->projection(), node->name(), saveRect, image->xRes(), image->yRes(), node->opacity()); } #include "SvgWriter.h" void KisNodeManager::saveVectorLayerAsImage() { KisShapeLayerSP shapeLayer = qobject_cast(activeNode().data()); if (!shapeLayer) { return; } KoFileDialog dialog(m_d->view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18nc("@title:window", "Export to SVG")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "image/svg+xml", "image/svg+xml"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; const QSizeF sizeInPx = m_d->view->image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / m_d->view->image()->xRes(), sizeInPx.height() / m_d->view->image()->yRes()); QList shapes = shapeLayer->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); if (!writer.save(filename, sizeInPt, true)) { QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not save to svg: %1", filename)); } } void KisNodeManager::slotSplitAlphaIntoMask() { KisNodeSP node = activeNode(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice()); KisPaintDeviceSP srcDevice = node->paintDevice(); const KoColorSpace *srcCS = srcDevice->colorSpace(); const QRect processRect = srcDevice->exactBounds() | srcDevice->defaultBounds()->bounds(); KisPaintDeviceSP selectionDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask")); KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice); KisSequentialIterator srcIt(srcDevice, processRect); KisSequentialIterator dstIt(selectionDevice, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *srcPtr = srcIt.rawData(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1); } m_d->commandsAdapter.addExtraCommand(transaction.endAndTake()); createNode("KisTransparencyMask", false, selectionDevice); m_d->commandsAdapter.endMacro(); } void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers) { KisNodeSP node = q->activeNode(); KisNodeSP parentNode = node->parent(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask")); if (writeToLayers && !parentNode->hasEditablePaintDevice()) { QMessageBox::information(view->mainWindow(), i18nc("@title:window", "Layer %1 is not editable", parentNode->name()), i18n("Cannot write alpha channel of " "the parent layer \"%1\".\n" "The operation will be cancelled.", parentNode->name())); return; } KisPaintDeviceSP dstDevice; if (writeToLayers) { KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice()); dstDevice = parentNode->paintDevice(); } else { KisPaintDeviceSP copyDevice = parentNode->paintDevice(); if (!copyDevice) { copyDevice = parentNode->original(); } dstDevice = new KisPaintDevice(*copyDevice); } const KoColorSpace *dstCS = dstDevice->colorSpace(); KisPaintDeviceSP selectionDevice = node->paintDevice(); KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1); const QRect processRect = selectionDevice->exactBounds() | dstDevice->exactBounds() | selectionDevice->defaultBounds()->bounds(); QScopedPointer transaction; if (writeToLayers) { commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer")); transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice)); } KisSequentialIterator srcIt(selectionDevice, processRect); KisSequentialIterator dstIt(dstDevice, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *alpha8Ptr = srcIt.rawData(); quint8 *dstPtr = dstIt.rawData(); dstCS->setOpacity(dstPtr, *alpha8Ptr, 1); } if (writeToLayers) { commandsAdapter.addExtraCommand(transaction->endAndTake()); commandsAdapter.removeNode(node); commandsAdapter.endMacro(); } else { KisImageWSP image = view->image(); QRect saveRect = image->bounds(); saveDeviceAsImage(dstDevice, parentNode->name(), saveRect, image->xRes(), image->yRes(), OPACITY_OPAQUE_U8); } } void KisNodeManager::slotSplitAlphaWrite() { m_d->mergeTransparencyMaskAsAlpha(true); } void KisNodeManager::slotSplitAlphaSaveMerged() { m_d->mergeTransparencyMaskAsAlpha(false); } void KisNodeManager::toggleLock() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; bool isLocked = active->userLocked(); for (auto &node : nodes) { node->setUserLocked(!isLocked); } } void KisNodeManager::toggleVisibility() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; bool isVisible = active->visible(); for (auto &node : nodes) { node->setVisible(!isVisible); node->setDirty(); } } void KisNodeManager::toggleAlphaLock() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; auto layer = qobject_cast(active.data()); if (!layer) { return; } bool isAlphaLocked = layer->alphaLocked(); for (auto &node : nodes) { auto layer = qobject_cast(node.data()); if (layer) { layer->setAlphaLocked(!isAlphaLocked); } } } void KisNodeManager::toggleInheritAlpha() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; auto layer = qobject_cast(active.data()); if (!layer) { return; } bool isAlphaDisabled = layer->alphaChannelDisabled(); for (auto &node : nodes) { auto layer = qobject_cast(node.data()); if (layer) { layer->disableAlphaChannel(!isAlphaDisabled); node->setDirty(); } } } void KisNodeManager::cutLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); if (nodes.isEmpty()) return; KisClipboard::instance()->setLayers(nodes, m_d->view->image(), false); KUndo2MagicString actionName = kundo2_i18n("Cut Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::copyLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); KisClipboard::instance()->setLayers(nodes, m_d->view->image(), true); } void KisNodeManager::pasteLayersFromClipboard() { const QMimeData *data = KisClipboard::instance()->layersMimeData(); if (!data) return; KisNodeSP activeNode = this->activeNode(); KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(dummiesFacade); const bool copyNode = false; KisImageSP image = m_d->view->image(); KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode); KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0; KisMimeData::insertMimeLayers(data, image, shapeController, parentDummy, aboveThisDummy, copyNode, nodeInsertionAdapter()); } void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild) { KisNodeSP active = activeNode(); if (!active) return; KisImageSP image = m_d->view->image(); QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName(); KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8); KisNodeList nodes1; nodes1 << group; KisNodeList nodes2; nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes()); KisLayerUtils::filterMergableNodes(nodes2); if (nodes2.size() == 0) return; if (KisLayerUtils::checkIsChildOf(active, nodes2)) { active = nodes2.first(); } KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; juggler->addNode(nodes1, parent, aboveThis); juggler->moveNode(nodes2, group, 0); *newGroup = group; *newLastChild = nodes2.last(); } void KisNodeManager::createQuickGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; createQuickGroupImpl(juggler, "", &parent, &above); } void KisNodeManager::createQuickClippingGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; KisImageSP image = m_d->view->image(); createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above); KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace()); maskLayer->disableAlphaChannel(true); juggler->addNode(KisNodeList() << maskLayer, parent, above); } void KisNodeManager::quickUngroup() { KisNodeSP active = activeNode(); if (!active) return; KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup"); if (parent && dynamic_cast(active.data())) { KisNodeList nodes = active->childNodes(QStringList(), KoProperties()); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, active); juggler->removeNode(KisNodeList() << active); } else if (parent && parent->parent()) { KisNodeSP grandParent = parent->parent(); KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties()); KisNodeList allSelectedNodes = selectedNodes(); const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(allSelectedNodes, grandParent, parent); if (removeParent) { juggler->removeNode(KisNodeList() << parent); } } } void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps) { KisImageSP image = m_d->view->image(); KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true); KisNodeList selectedNodes = this->selectedNodes(); if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) { nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true); } if (!nodes.isEmpty()) { slotImageRequestNodeReselection(nodes.last(), nodes); } } void KisNodeManager::selectAllNodes() { KoProperties props; selectLayersImpl(props, props); } void KisNodeManager::selectVisibleNodes() { KoProperties props; props.setProperty("visible", true); KoProperties invertedProps; invertedProps.setProperty("visible", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectLockedNodes() { KoProperties props; props.setProperty("locked", true); KoProperties invertedProps; invertedProps.setProperty("locked", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectInvisibleNodes() { KoProperties props; props.setProperty("visible", false); KoProperties invertedProps; invertedProps.setProperty("visible", true); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectUnlockedNodes() { KoProperties props; props.setProperty("locked", false); KoProperties invertedProps; invertedProps.setProperty("locked", true); selectLayersImpl(props, invertedProps); } diff --git a/plugins/dockers/animation/timeline_docker.cpp b/plugins/dockers/animation/timeline_docker.cpp index 66e807f107..1c6a74b1c6 100644 --- a/plugins/dockers/animation/timeline_docker.cpp +++ b/plugins/dockers/animation/timeline_docker.cpp @@ -1,164 +1,166 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_docker.h" #include "kis_canvas2.h" #include "kis_image.h" #include #include "KisViewManager.h" #include "kis_paint_layer.h" #include "KisDocument.h" #include "kis_dummies_facade.h" #include "kis_shape_controller.h" #include "kis_action.h" #include "kis_action_manager.h" #include "timeline_frames_model.h" #include "timeline_frames_view.h" #include "kis_animation_frame_cache.h" #include "kis_image_animation_interface.h" #include "kis_signal_auto_connection.h" #include "kis_node_manager.h" #include struct TimelineDocker::Private { Private(QWidget *parent) : model(new TimelineFramesModel(parent)), view(new TimelineFramesView(parent)) { view->setModel(model); } TimelineFramesModel *model; TimelineFramesView *view; QPointer canvas; KisSignalAutoConnectionsStore canvasConnections; }; TimelineDocker::TimelineDocker() : QDockWidget(i18n("Timeline")) , m_d(new Private(this)) { setWidget(m_d->view); } TimelineDocker::~TimelineDocker() { } struct NodeManagerInterface : TimelineFramesModel::NodeManipulationInterface { NodeManagerInterface(KisNodeManager *manager) : m_manager(manager) {} KisLayerSP addPaintLayer() const override { return m_manager->createPaintLayer(); } void removeNode(KisNodeSP node) const override { m_manager->removeSingleNode(node); } bool setNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const override { return m_manager->trySetNodeProperties(node, image, properties); } private: KisNodeManager *m_manager; }; void TimelineDocker::setCanvas(KoCanvasBase * canvas) { if (canvas && m_d->canvas == canvas) return; if (m_d->model->hasConnectionToCanvas()) { m_d->canvasConnections.clear(); - m_d->model->setDummiesFacade(0, 0); + m_d->model->setDummiesFacade(0, 0, 0); m_d->model->setFrameCache(0); m_d->model->setAnimationPlayer(0); m_d->model->setNodeManipulationInterface(0); if (m_d->canvas) { m_d->canvas->disconnectCanvasObserver(this); } } m_d->canvas = dynamic_cast(canvas); setEnabled(m_d->canvas != 0); if(m_d->canvas) { KisDocument *doc = static_cast(m_d->canvas->imageView()->document()); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); - m_d->model->setDummiesFacade(kritaShapeController, m_d->canvas->image()); + m_d->model->setDummiesFacade(kritaShapeController, + m_d->canvas->image(), + m_d->canvas->viewManager()->nodeManager()->nodeDisplayModeAdapter()); slotUpdateFrameCache(); m_d->model->setAnimationPlayer(m_d->canvas->animationPlayer()); m_d->model->setNodeManipulationInterface( new NodeManagerInterface(m_d->canvas->viewManager()->nodeManager())); m_d->canvasConnections.addConnection( m_d->canvas->viewManager()->nodeManager(), SIGNAL(sigNodeActivated(KisNodeSP)), m_d->model, SLOT(slotCurrentNodeChanged(KisNodeSP))); m_d->canvasConnections.addConnection( m_d->model, SIGNAL(requestCurrentNodeChanged(KisNodeSP)), m_d->canvas->viewManager()->nodeManager(), SLOT(slotNonUiActivatedNode(KisNodeSP))); m_d->model->slotCurrentNodeChanged(m_d->canvas->viewManager()->activeNode()); m_d->canvasConnections.addConnection( m_d->canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons())); m_d->canvasConnections.addConnection( m_d->canvas, SIGNAL(sigCanvasEngineChanged()), this, SLOT(slotUpdateFrameCache())); } } void TimelineDocker::slotUpdateIcons() { if (m_d->view) { m_d->view->slotUpdateIcons(); } } void TimelineDocker::slotUpdateFrameCache() { m_d->model->setFrameCache(m_d->canvas->frameCache()); } void TimelineDocker::unsetCanvas() { setCanvas(0); } void TimelineDocker::setViewManager(KisViewManager *view) { KisActionManager *actionManager = view->actionManager(); m_d->view->setShowInTimeline(actionManager->actionByName("show_in_timeline")); m_d->view->setActionManager(actionManager); } diff --git a/plugins/dockers/animation/timeline_frames_index_converter.cpp b/plugins/dockers/animation/timeline_frames_index_converter.cpp index 34fefc1513..c33b7dc9f9 100644 --- a/plugins/dockers/animation/timeline_frames_index_converter.cpp +++ b/plugins/dockers/animation/timeline_frames_index_converter.cpp @@ -1,139 +1,152 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_index_converter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" TimelineFramesIndexConverter::TimelineFramesIndexConverter(KisDummiesFacadeBase *dummiesFacade) : m_dummiesFacade(dummiesFacade), - m_activeDummy(0) + m_activeDummy(0), + m_showGlobalSelectionMask(false) { } TimelineFramesIndexConverter::~TimelineFramesIndexConverter() { } bool TimelineFramesIndexConverter::calcNodesInPath(KisNodeDummy *root, int &startCount, KisNodeDummy *endDummy) { if (isDummyVisible(root)) { if (endDummy && root == endDummy) { return true; } startCount++; } KisNodeDummy *dummy = root->lastChild(); while (dummy) { if (calcNodesInPath(dummy, startCount, endDummy)) { return true; } dummy = dummy->prevSibling(); } return false; } KisNodeDummy* TimelineFramesIndexConverter::findNodeFromRow(KisNodeDummy *root, int &startCount) { if (isDummyVisible(root)) { if (!startCount) { return root; } startCount--; } KisNodeDummy *dummy = root->lastChild(); while (dummy) { KisNodeDummy *found = findNodeFromRow(dummy, startCount); if (found) return found; dummy = dummy->prevSibling(); } return 0; } KisNodeDummy* TimelineFramesIndexConverter::dummyFromRow(int row) { KisNodeDummy *root = m_dummiesFacade->rootDummy(); if(!root) return 0; return findNodeFromRow(root, row); } int TimelineFramesIndexConverter::rowForDummy(KisNodeDummy *dummy) { if (!dummy) return -1; KisNodeDummy *root = m_dummiesFacade->rootDummy(); if(!root) return -1; int count = 0; return calcNodesInPath(root, count, dummy) ? count : -1; } int TimelineFramesIndexConverter::rowCount() { KisNodeDummy *root = m_dummiesFacade->rootDummy(); if(!root) return 0; int count = 0; calcNodesInPath(root, count, 0); return count; } KisNodeDummy* TimelineFramesIndexConverter::activeDummy() const { return m_activeDummy; } void TimelineFramesIndexConverter::updateActiveDummy(KisNodeDummy *dummy, bool *oldRemoved, bool *newAdded) { if (m_activeDummy == dummy) return; if (m_activeDummy && !m_activeDummy->node()->useInTimeline()) { *oldRemoved = true; } m_activeDummy = dummy; if (m_activeDummy && !m_activeDummy->node()->useInTimeline()) { *newAdded = true; } } void TimelineFramesIndexConverter::notifyDummyRemoved(KisNodeDummy *dummy) { if (m_activeDummy && m_activeDummy == dummy) { m_activeDummy = 0; } } +void TimelineFramesIndexConverter::setShowGlobalSelectionMask(bool value) +{ + m_showGlobalSelectionMask = value; +} + +bool TimelineFramesIndexConverter::isDummyAvailableForTimeline(KisNodeDummy *dummy) const +{ + return dummy->isGUIVisible(m_showGlobalSelectionMask); +} + bool TimelineFramesIndexConverter::isDummyVisible(KisNodeDummy *dummy) const { - return (dummy->node()->useInTimeline() && dummy->parent()) || dummy == m_activeDummy; + return (isDummyAvailableForTimeline(dummy) && + dummy->node()->useInTimeline()) || + dummy == m_activeDummy; } diff --git a/plugins/dockers/animation/timeline_frames_index_converter.h b/plugins/dockers/animation/timeline_frames_index_converter.h index 9974d5df81..4a6f273368 100644 --- a/plugins/dockers/animation/timeline_frames_index_converter.h +++ b/plugins/dockers/animation/timeline_frames_index_converter.h @@ -1,54 +1,57 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_FRAMES_INDEX_CONVERTER_H #define __TIMELINE_FRAMES_INDEX_CONVERTER_H #include "kritaanimationdocker_export.h" class KisNodeDummy; class KisDummiesFacadeBase; class KRITAANIMATIONDOCKER_EXPORT TimelineFramesIndexConverter { public: TimelineFramesIndexConverter(KisDummiesFacadeBase *dummiesFacade); ~TimelineFramesIndexConverter(); KisNodeDummy* dummyFromRow(int row); int rowForDummy(KisNodeDummy *dummy); int rowCount(); KisNodeDummy* activeDummy() const; void updateActiveDummy(KisNodeDummy *dummy, bool *oldRemoved, bool *newAdded); void notifyDummyRemoved(KisNodeDummy *dummy); + void setShowGlobalSelectionMask(bool value); + bool isDummyAvailableForTimeline(KisNodeDummy *dummy) const; bool isDummyVisible(KisNodeDummy *dummy) const; private: KisNodeDummy* findNodeFromRow(KisNodeDummy *root, int &startCount); bool calcNodesInPath(KisNodeDummy *root, int &startCount, KisNodeDummy *endDummy); private: KisDummiesFacadeBase *m_dummiesFacade; KisNodeDummy *m_activeDummy; + bool m_showGlobalSelectionMask; }; #endif /* __TIMELINE_FRAMES_INDEX_CONVERTER_H */ diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index 92007fabdc..6d3cb1aabf 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -1,1023 +1,1026 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_model.h" #include #include #include #include #include #include #include "kis_layer.h" #include "kis_config.h" #include "kis_global.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_undo_adapter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" +#include "KisNodeDisplayModeAdapter.h" #include "kis_signal_compressor.h" #include "kis_signal_compressor_with_param.h" #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include #include #include "kis_animation_utils.h" #include "timeline_color_scheme.h" #include "kis_node_model.h" #include "kis_projection_leaf.h" #include "kis_time_range.h" #include "kis_node_view_color_scheme.h" #include "krita_utils.h" #include "KisPart.h" #include #include "KisDocument.h" #include "KisViewManager.h" #include "kis_processing_applicator.h" #include #include "kis_node_uuid_info.h" struct TimelineFramesModel::Private { Private() : activeLayerIndex(0), dummiesFacade(0), needFinishInsertRows(false), needFinishRemoveRows(false), updateTimer(200, KisSignalCompressor::FIRST_INACTIVE), parentOfRemovedNode(0) {} int activeLayerIndex; QPointer dummiesFacade; KisImageWSP image; bool needFinishInsertRows; bool needFinishRemoveRows; QList updateQueue; KisSignalCompressor updateTimer; KisNodeDummy* parentOfRemovedNode; QScopedPointer converter; QScopedPointer nodeInterface; QPersistentModelIndex lastClickedIndex; QVariant layerName(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); return dummy->node()->name(); } bool layerEditable(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return true; return dummy->node()->visible() && !dummy->node()->userLocked(); } bool frameExists(int row, int column) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); return (primaryChannel && primaryChannel->keyframeAt(column)); } bool frameHasContent(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return false; // first check if we are a key frame KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return false; return frame->hasContent(); } bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; Q_FOREACH(KisKeyframeChannel *channel, dummy->node()->keyframeChannels()) { if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) { return true; } } return false; } int frameColorLabel(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return -1; return frame->colorLabel(); } void setFrameColorLabel(int row, int column, int color) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return; frame->setColorLabel(color); } int layerColorLabel(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; return dummy->node()->colorLabelIndex(); } QVariant layerProperties(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); PropertyList props = dummy->node()->sectionModelProperties(); return QVariant::fromValue(props); } bool setLayerProperties(int row, PropertyList props) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; nodeInterface->setNodeProperties(dummy->node(), image, props); return true; } bool addKeyframe(int row, int column, bool copy) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) return false; KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy); return true; } bool addNewLayer(int row) { Q_UNUSED(row); if (nodeInterface) { KisLayerSP layer = nodeInterface->addPaintLayer(); layer->setUseInTimeline(true); } return true; } bool removeLayer(int row) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; if (nodeInterface) { nodeInterface->removeNode(dummy->node()); } return true; } }; TimelineFramesModel::TimelineFramesModel(QObject *parent) : ModelWithExternalNotifications(parent), m_d(new Private) { connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue())); } TimelineFramesModel::~TimelineFramesModel() { } bool TimelineFramesModel::hasConnectionToCanvas() const { return m_d->dummiesFacade; } void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface) { m_d->nodeInterface.reset(iface); } KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const { /** * The dummy might not exist because the user could (quickly) change * active layer and the list of the nodes in m_d->converter will change. */ KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); return dummy ? dummy->node() : 0; } QMap TimelineFramesModel::channelsAt(QModelIndex index) const { KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row()); return srcDummy->node()->keyframeChannels(); } -void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image) +void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, + KisImageSP image, + KisNodeDisplayModeAdapter *displayModeAdapter) { KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade; if (m_d->dummiesFacade && m_d->image) { m_d->image->animationInterface()->disconnect(this); m_d->image->disconnect(this); m_d->dummiesFacade->disconnect(this); } m_d->image = image; KisTimeBasedItemModel::setImage(image); m_d->dummiesFacade = dummiesFacade; m_d->converter.reset(); if (m_d->dummiesFacade) { - m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade)); + m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade, displayModeAdapter)); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); connect(m_d->image->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image, SIGNAL(sigImageModified()), SLOT(slotImageContentChanged())); } if (m_d->dummiesFacade != oldDummiesFacade) { beginResetModel(); endResetModel(); } if (m_d->dummiesFacade) { emit sigInfiniteTimelineUpdateNeeded(); emit sigAudioChannelChanged(); } } void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateTimer.start(); } void TimelineFramesModel::slotImageContentChanged() { if (m_d->activeLayerIndex < 0) return; KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); if (!dummy) return; slotDummyChanged(dummy); } void TimelineFramesModel::processUpdateQueue() { if (!m_d->converter) return; Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { int row = m_d->converter->rowForDummy(dummy); if (row >= 0) { emit headerDataChanged (Qt::Vertical, row, row); emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1)); } } m_d->updateQueue.clear(); } void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node) { if (!node) { m_d->activeLayerIndex = -1; return; } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); if (!dummy) { // It's perfectly normal that dummyForNode returns 0; that happens // when views get activated while Krita is closing down. return; } m_d->converter->updateActiveDummy(dummy); const int row = m_d->converter->rowForDummy(dummy); if (row < 0) { qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!"; } if (row >= 0 && m_d->activeLayerIndex != row) { setData(index(row, 0), true, ActiveLayerRole); } } int TimelineFramesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if(!m_d->dummiesFacade) return 0; return m_d->converter->rowCount(); } QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const { if(!m_d->dummiesFacade) return QVariant(); switch (role) { case ActiveLayerRole: { return index.row() == m_d->activeLayerIndex; } case FrameEditableRole: { return m_d->layerEditable(index.row()); } case FrameHasContent: { return m_d->frameHasContent(index.row(), index.column()); } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } case SpecialKeyframeExists: { return m_d->specialKeyframeExists(index.row(), index.column()); } case FrameColorLabelIndexRole: { int label = m_d->frameColorLabel(index.row(), index.column()); return label > 0 ? label : QVariant(); } case Qt::DisplayRole: { return m_d->layerName(index.row()); } case Qt::TextAlignmentRole: { return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } case KoResourceModel::LargeThumbnailRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); if (!dummy) { return QVariant(); } const int maxSize = 200; QSize size = dummy->node()->extent().size(); size.scale(maxSize, maxSize, Qt::KeepAspectRatio); if (size.width() == 0 || size.height() == 0) { // No thumbnail can be shown if there isn't width or height... return QVariant(); } QImage image(dummy->node()->createThumbnailForFrame(size.width(), size.height(), index.column())); return image; } } return ModelWithExternalNotifications::data(index, role); } bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || !m_d->dummiesFacade) return false; switch (role) { case ActiveLayerRole: { if (value.toBool() && index.row() != m_d->activeLayerIndex) { int prevLayer = m_d->activeLayerIndex; m_d->activeLayerIndex = index.row(); emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1)); emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1)); emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer); emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex); KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); KIS_ASSERT_RECOVER(dummy) { return true; } emit requestCurrentNodeChanged(dummy->node()); emit sigEnsureRowVisible(m_d->activeLayerIndex); } break; } case FrameColorLabelIndexRole: { m_d->setFrameColorLabel(index.row(), index.column(), value.toInt()); } break; } return ModelWithExternalNotifications::setData(index, value, role); } QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const { if(!m_d->dummiesFacade) return QVariant(); if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: return section == m_d->activeLayerIndex; case Qt::DisplayRole: { QVariant value = headerData(section, orientation, Qt::ToolTipRole); if (!value.isValid()) return value; QString name = value.toString(); const int maxNameSize = 13; if (name.size() > maxNameSize) { name = QString("%1...").arg(name.left(maxNameSize)); } return name; } case Qt::TextColorRole: { // WARNING: this role doesn't work for header views! Use // bold font to show isolated mode instead! return QVariant(); } case Qt::FontRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); KisNodeSP node = dummy->node(); QFont baseFont; if (node->projectionLeaf()->isDroppedNode()) { baseFont.setStrikeOut(true); } else if (m_d->image && m_d->image->isolatedModeRoot() && KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) { baseFont.setBold(true); } return baseFont; } case Qt::ToolTipRole: { return m_d->layerName(section); } case TimelinePropertiesRole: { return QVariant::fromValue(m_d->layerProperties(section)); } case OtherLayersRole: { TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); return QVariant::fromValue(list); } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); return dummy->node()->useInTimeline(); } case Qt::BackgroundRole: { int label = m_d->layerColorLabel(section); if (label > 0) { KisNodeViewColorScheme scm; QColor color = scm.colorLabel(label); QPalette pal = qApp->palette(); color = KritaUtils::blendColors(color, pal.color(QPalette::Button), 0.3); return QBrush(color); } else { return QVariant(); } } } } return ModelWithExternalNotifications::headerData(section, orientation, role); } bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (!m_d->dummiesFacade) return false; if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: { setData(index(section, 0), value, role); break; } case TimelinePropertiesRole: { TimelineFramesModel::PropertyList props = value.value(); int result = m_d->setLayerProperties(section, props); emit headerDataChanged (Qt::Vertical, section, section); return result; } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return false; dummy->node()->setUseInTimeline(value.toBool()); return true; } } } return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role); } Qt::DropActions TimelineFramesModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions TimelineFramesModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList TimelineFramesModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-frame"); return types; } void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index) { m_d->lastClickedIndex = index; } QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const { return mimeDataExtended(indexes, m_d->lastClickedIndex, UndefinedPolicy); } QMimeData *TimelineFramesModel::mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, TimelineFramesModel::MimeCopyPolicy copyPolicy) const { QMimeData *data = new QMimeData(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); const int baseRow = baseIndex.row(); const int baseColumn = baseIndex.column(); const QByteArray uuidDataRoot = m_d->image->root()->uuid().toRfc4122(); stream << int(uuidDataRoot.size()); stream.writeRawData(uuidDataRoot.data(), uuidDataRoot.size()); stream << indexes.size(); stream << baseRow << baseColumn; Q_FOREACH (const QModelIndex &index, indexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } stream << index.row() - baseRow << index.column() - baseColumn; const QByteArray uuidData = node->uuid().toRfc4122(); stream << int(uuidData.size()); stream.writeRawData(uuidData.data(), uuidData.size()); } stream << int(copyPolicy); data->setData("application/x-krita-frame", encoded); return data; } inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col) { int size_UNUSED = 0; QDataStream stream(encoded, QIODevice::ReadOnly); stream >> size_UNUSED >> *row >> *col; } bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index) { if (!index.isValid()) return false; /** * Now we support D&D around any layer, so just return 'true' all * the time. */ return true; } bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); return dropMimeDataExtended(data, action, parent); } bool TimelineFramesModel::dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved) { bool result = false; if ((action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) return result; QByteArray encoded = data->data("application/x-krita-frame"); QDataStream stream(&encoded, QIODevice::ReadOnly); int uuidLenRoot = 0; stream >> uuidLenRoot; QByteArray uuidDataRoot(uuidLenRoot, '\0'); stream.readRawData(uuidDataRoot.data(), uuidLenRoot); QUuid nodeUuidRoot = QUuid::fromRfc4122(uuidDataRoot); KisPart *partInstance = KisPart::instance(); QList> documents = partInstance->documents(); KisImageSP srcImage = 0; Q_FOREACH(KisDocument *doc, documents) { KisImageSP tmpSrcImage = doc->image(); if (tmpSrcImage->root()->uuid() == nodeUuidRoot) { srcImage = tmpSrcImage; break; } } if (!srcImage) { KisPart *kisPartInstance = KisPart::instance(); kisPartInstance->currentMainwindow()->viewManager()->showFloatingMessage( i18n("Dropped frames are not available in this Krita instance") , QIcon()); return false; } int size, baseRow, baseColumn; stream >> size >> baseRow >> baseColumn; const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow); KisAnimationUtils::FrameMovePairList frameMoves; for (int i = 0; i < size; i++) { int relRow, relColumn; stream >> relRow >> relColumn; const int srcRow = baseRow + relRow; const int srcColumn = baseColumn + relColumn; int uuidLen = 0; stream >> uuidLen; QByteArray uuidData(uuidLen, '\0'); stream.readRawData(uuidData.data(), uuidLen); QUuid nodeUuid = QUuid::fromRfc4122(uuidData); KisNodeSP srcNode; if (!nodeUuid.isNull()) { KisNodeUuidInfo nodeInfo(nodeUuid); srcNode = nodeInfo.findNode(srcImage->root()); } else { QModelIndex index = this->index(srcRow, srcColumn); srcNode = nodeAt(index); } KIS_SAFE_ASSERT_RECOVER(srcNode) { continue; } const QModelIndex dstRowIndex = this->index(srcRow + offset.y(), 0); if (!dstRowIndex.isValid()) continue; KisNodeSP dstNode = nodeAt(dstRowIndex); KIS_SAFE_ASSERT_RECOVER(dstNode) { continue; } Q_FOREACH (KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisAnimationUtils::FrameItem srcItem(srcNode, channel->id(), srcColumn); KisAnimationUtils::FrameItem dstItem(dstNode, channel->id(), srcColumn + offset.x()); frameMoves << std::make_pair(srcItem, dstItem); } } MimeCopyPolicy copyPolicy = UndefinedPolicy; if (!stream.atEnd()) { int value = 0; stream >> value; copyPolicy = MimeCopyPolicy(value); } const bool copyFrames = copyPolicy == UndefinedPolicy ? action == Qt::CopyAction : copyPolicy == CopyFramesPolicy; if (dataMoved) { *dataMoved = !copyFrames; } KUndo2Command *cmd = 0; if (!frameMoves.isEmpty()) { KisImageBarrierLockerWithFeedback locker(m_d->image); cmd = KisAnimationUtils::createMoveKeyframesCommand(frameMoves, copyFrames, false, 0); } if (cmd) { KisProcessingApplicator::runSingleCommandStroke(m_d->image, cmd, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); } return cmd; } Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index); if (!index.isValid()) return flags; if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) { if (data(index, FrameEditableRole).toBool()) { flags |= Qt::ItemIsDragEnabled; } } /** * Basically we should forbid overrides only if we D&D a single frame * and allow it when we D&D multiple frames. But we cannot distinguish * it here... So allow all the time. */ flags |= Qt::ItemIsDropEnabled; return flags; } bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row > rowCount()) return false; bool result = m_d->addNewLayer(row); return result; } bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row >= rowCount()) return false; bool result = m_d->removeLayer(row); return result; } bool TimelineFramesModel::insertOtherLayer(int index, int dstRow) { Q_UNUSED(dstRow); TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); if (index < 0 || index >= list.size()) return false; list[index].dummy->node()->setUseInTimeline(true); dstRow = m_d->converter->rowForDummy(list[index].dummy); setData(this->index(dstRow, 0), true, ActiveLayerRole); return true; } int TimelineFramesModel::activeLayerRow() const { return m_d->activeLayerIndex; } bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false); } bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true); } bool TimelineFramesModel::insertFrames(int dstColumn, const QList &dstRows, int count, int timing) { if (dstRows.isEmpty() || count <= 0) return true; timing = qMax(timing, 1); KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count)); { KisImageBarrierLockerWithFeedback locker(m_d->image); QModelIndexList indexes; Q_FOREACH (int row, dstRows) { for (int column = dstColumn; column < columnCount(); column++) { indexes << index(row, column); } } setLastVisibleFrame(columnCount() + (count * timing) - 1); createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, false, parentCommand); Q_FOREACH (int row, dstRows) { KisNodeDummy *dummy = m_d->converter->dummyFromRow(row); if (!dummy) continue; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) continue; for (int column = dstColumn; column < dstColumn + (count * timing); column += timing) { KisAnimationUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Content.id(), column, false, parentCommand); } } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = dstColumn > oldTime ? dstColumn : dstColumn + (count * timing) - 1; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } bool TimelineFramesModel::insertHoldFrames(QModelIndexList selectedIndexes, int count) { if (selectedIndexes.isEmpty() || count == 0) return true; QScopedPointer parentCommand(new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count))); { KisImageBarrierLockerWithFeedback locker(m_d->image); QSet uniqueKeyframesInSelection; int minSelectedTime = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, selectedIndexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) continue; minSelectedTime = qMin(minSelectedTime, index.column()); KisKeyframeSP keyFrame = channel->activeKeyframeAt(index.column()); if (keyFrame) { uniqueKeyframesInSelection.insert(keyFrame); } } QList keyframesToMove; for (auto it = uniqueKeyframesInSelection.begin(); it != uniqueKeyframesInSelection.end(); ++it) { KisKeyframeSP keyframe = *it; KisKeyframeChannel *channel = keyframe->channel(); KisKeyframeSP nextKeyframe = channel->nextKeyframe(keyframe); if (nextKeyframe) { keyframesToMove << nextKeyframe; } } std::sort(keyframesToMove.begin(), keyframesToMove.end(), [] (KisKeyframeSP lhs, KisKeyframeSP rhs) { return lhs->time() > rhs->time(); }); if (keyframesToMove.isEmpty()) return true; const int maxColumn = columnCount(); if (count > 0) { setLastVisibleFrame(columnCount() + count); } Q_FOREACH (KisKeyframeSP keyframe, keyframesToMove) { int plannedFrameMove = count; if (count < 0) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(keyframe->time() > 0, false); KisKeyframeSP prevFrame = keyframe->channel()->previousKeyframe(keyframe); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(prevFrame, false); plannedFrameMove = qMax(count, prevFrame->time() - keyframe->time() + 1); minSelectedTime = qMin(minSelectedTime, prevFrame->time()); } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(keyframe->channel()->node()); KIS_SAFE_ASSERT_RECOVER(dummy) { continue; } const int row = m_d->converter->rowForDummy(dummy); KIS_SAFE_ASSERT_RECOVER(row >= 0) { continue; } QModelIndexList indexes; for (int column = keyframe->time(); column < maxColumn; column++) { indexes << index(row, column); } createOffsetFramesCommand(indexes, QPoint(plannedFrameMove, 0), false, true, parentCommand.data()); } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = minSelectedTime; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand.data()); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand.take(), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } QString TimelineFramesModel::audioChannelFileName() const { return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString(); } void TimelineFramesModel::setAudioChannelFileName(const QString &fileName) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioChannelFileName(fileName); } bool TimelineFramesModel::isAudioMuted() const { return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false; } void TimelineFramesModel::setAudioMuted(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioMuted(value); } qreal TimelineFramesModel::audioVolume() const { return m_d->image ? m_d->image->animationInterface()->audioVolume() : 0.5; } void TimelineFramesModel::setAudioVolume(qreal value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioVolume(value); } void TimelineFramesModel::setFullClipRangeStart(int column) { m_d->image->animationInterface()->setFullClipRangeStartTime(column); } void TimelineFramesModel::setFullClipRangeEnd(int column) { m_d->image->animationInterface()->setFullClipRangeEndTime(column); } diff --git a/plugins/dockers/animation/timeline_frames_model.h b/plugins/dockers/animation/timeline_frames_model.h index 8f8d5edabe..21880dd76c 100644 --- a/plugins/dockers/animation/timeline_frames_model.h +++ b/plugins/dockers/animation/timeline_frames_model.h @@ -1,154 +1,157 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_FRAMES_MODEL_H #define __TIMELINE_FRAMES_MODEL_H #include #include #include "kritaanimationdocker_export.h" #include "kis_node_model.h" #include "kis_types.h" #include "kis_node.h" #include "timeline_node_list_keeper.h" class KisNodeDummy; class KisDummiesFacadeBase; class KisAnimationPlayer; +class KisNodeDisplayModeAdapter; class KRITAANIMATIONDOCKER_EXPORT TimelineFramesModel : public TimelineNodeListKeeper::ModelWithExternalNotifications { Q_OBJECT public: enum MimeCopyPolicy { UndefinedPolicy = 0, MoveFramesPolicy, CopyFramesPolicy }; public: TimelineFramesModel(QObject *parent); ~TimelineFramesModel() override; bool hasConnectionToCanvas() const; - void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image); + void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, + KisImageSP image, + KisNodeDisplayModeAdapter *displayModeAdapter); bool canDropFrameData(const QMimeData *data, const QModelIndex &index); bool insertOtherLayer(int index, int dstRow); int activeLayerRow() const; bool createFrame(const QModelIndex &dstIndex); bool copyFrame(const QModelIndex &dstIndex); bool insertFrames(int dstColumn, const QList &dstRows, int count, int timing = 1); bool insertHoldFrames(QModelIndexList selectedIndexes, int count); QString audioChannelFileName() const; void setAudioChannelFileName(const QString &fileName); bool isAudioMuted() const; void setAudioMuted(bool value); qreal audioVolume() const; void setAudioVolume(qreal value); void setFullClipRangeStart(int column); void setFullClipRangeEnd(int column); void setLastClickedIndex(const QModelIndex &index); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override; Qt::DropActions supportedDragActions() const override; Qt::DropActions supportedDropActions() const override; QStringList mimeTypes() const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; QMimeData *mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, MimeCopyPolicy copyPolicy) const; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; bool dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved = 0); Qt::ItemFlags flags(const QModelIndex &index) const override; bool insertRows(int row, int count, const QModelIndex &parent) override; bool removeRows(int row, int count, const QModelIndex &parent) override; enum ItemDataRole { ActiveLayerRole = KisTimeBasedItemModel::UserRole, TimelinePropertiesRole, OtherLayersRole, LayerUsedInTimelineRole, FrameColorLabelIndexRole }; // metatype is added by the original implementation typedef KisBaseNode::Property Property; typedef KisBaseNode::PropertyList PropertyList; typedef TimelineNodeListKeeper::OtherLayer OtherLayer; typedef TimelineNodeListKeeper::OtherLayersList OtherLayersList; struct NodeManipulationInterface { virtual ~NodeManipulationInterface() {} virtual KisLayerSP addPaintLayer() const = 0; virtual void removeNode(KisNodeSP node) const = 0; virtual bool setNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const = 0; }; /** * NOTE: the model has an ownership over the interface, that is it'll * be deleted automatically later */ void setNodeManipulationInterface(NodeManipulationInterface *iface); KisNodeSP nodeAt(QModelIndex index) const override; protected: QMap channelsAt(QModelIndex index) const override; private Q_SLOTS: void slotDummyChanged(KisNodeDummy *dummy); void slotImageContentChanged(); void processUpdateQueue(); public Q_SLOTS: void slotCurrentNodeChanged(KisNodeSP node); Q_SIGNALS: void requestCurrentNodeChanged(KisNodeSP node); void sigInfiniteTimelineUpdateNeeded(); void sigAudioChannelChanged(); void sigEnsureRowVisible(int row); private: struct Private; const QScopedPointer m_d; }; #endif /* __TIMELINE_FRAMES_MODEL_H */ diff --git a/plugins/dockers/animation/timeline_node_list_keeper.cpp b/plugins/dockers/animation/timeline_node_list_keeper.cpp index 5631972eb1..775bc2a149 100644 --- a/plugins/dockers/animation/timeline_node_list_keeper.cpp +++ b/plugins/dockers/animation/timeline_node_list_keeper.cpp @@ -1,249 +1,287 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_node_list_keeper.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "timeline_frames_index_converter.h" #include #include #include "kis_keyframe_channel.h" +#include "KisNodeDisplayModeAdapter.h" struct TimelineNodeListKeeper::Private { Private(TimelineNodeListKeeper *_q, ModelWithExternalNotifications *_model, - KisDummiesFacadeBase *_dummiesFacade) + KisDummiesFacadeBase *_dummiesFacade, + KisNodeDisplayModeAdapter *_displayModeAdapter) : q(_q), model(_model), dummiesFacade(_dummiesFacade), + displayModeAdapter(_displayModeAdapter), + showGlobalSelectionMask(_displayModeAdapter->showGlobalSelectionMask()), converter(dummiesFacade) { + converter.setShowGlobalSelectionMask(showGlobalSelectionMask); } TimelineNodeListKeeper *q; ModelWithExternalNotifications *model; KisDummiesFacadeBase *dummiesFacade; + KisNodeDisplayModeAdapter *displayModeAdapter; + bool showGlobalSelectionMask; TimelineFramesIndexConverter converter; QVector dummiesList; QSignalMapper dummiesUpdateMapper; QSet connectionsSet; void populateDummiesList() { const int rowCount = converter.rowCount(); for (int i = 0; i < rowCount; ++i) { KisNodeDummy *dummy = converter.dummyFromRow(i); dummiesList.append(dummy); tryConnectDummy(dummy); } } void tryConnectDummy(KisNodeDummy *dummy); void disconnectDummy(KisNodeDummy *dummy); + + void findOtherLayers(KisNodeDummy *root, + TimelineNodeListKeeper::OtherLayersList *list, + const QString &prefix); + }; -TimelineNodeListKeeper::TimelineNodeListKeeper(ModelWithExternalNotifications *model, KisDummiesFacadeBase *dummiesFacade) - : m_d(new Private(this, model, dummiesFacade)) +TimelineNodeListKeeper::TimelineNodeListKeeper(ModelWithExternalNotifications *model, + KisDummiesFacadeBase *dummiesFacade, + KisNodeDisplayModeAdapter *displayModeAdapter) + : m_d(new Private(this, model, dummiesFacade, displayModeAdapter)) { KIS_ASSERT_RECOVER_RETURN(m_d->dummiesFacade); connect(m_d->dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)), SLOT(slotEndInsertDummy(KisNodeDummy*))); connect(m_d->dummiesFacade, SIGNAL(sigBeginRemoveDummy(KisNodeDummy*)), SLOT(slotBeginRemoveDummy(KisNodeDummy*))); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); m_d->populateDummiesList(); connect(&m_d->dummiesUpdateMapper, SIGNAL(mapped(QObject*)), SLOT(slotUpdateDummyContent(QObject*))); + + connect(m_d->displayModeAdapter, SIGNAL(sigNodeDisplayModeChanged(bool, bool)), SLOT(slotDisplayModeChanged())); } TimelineNodeListKeeper::~TimelineNodeListKeeper() { } KisNodeDummy* TimelineNodeListKeeper::dummyFromRow(int row) { if (row >= 0 && row < m_d->dummiesList.size()) { return m_d->dummiesList[row]; } return 0; } int TimelineNodeListKeeper::rowForDummy(KisNodeDummy *dummy) { return m_d->dummiesList.indexOf(dummy); } int TimelineNodeListKeeper::rowCount() { return m_d->dummiesList.size(); } void TimelineNodeListKeeper::updateActiveDummy(KisNodeDummy *dummy) { bool oldRemoved = false; bool newAdded = false; KisNodeDummy *oldActiveDummy = m_d->converter.activeDummy(); m_d->converter.updateActiveDummy(dummy, &oldRemoved, &newAdded); if (oldRemoved) { slotBeginRemoveDummy(oldActiveDummy); } if (newAdded) { slotEndInsertDummy(dummy); } } void TimelineNodeListKeeper::slotUpdateDummyContent(QObject *_dummy) { KisNodeDummy *dummy = qobject_cast(_dummy); int pos = m_d->converter.rowForDummy(dummy); if (pos < 0) return; QModelIndex index0 = m_d->model->index(pos, 0); QModelIndex index1 = m_d->model->index(pos, m_d->model->columnCount() - 1); m_d->model->callIndexChanged(index0, index1); } void TimelineNodeListKeeper::Private::tryConnectDummy(KisNodeDummy *dummy) { QMap channels = dummy->node()->keyframeChannels(); if (channels.isEmpty()) { if (connectionsSet.contains(dummy)) { connectionsSet.remove(dummy); } return; } if (connectionsSet.contains(dummy)) return; Q_FOREACH(KisKeyframeChannel *channel, channels) { connect(channel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), &dummiesUpdateMapper, SLOT(map())); connect(channel, SIGNAL(sigKeyframeAboutToBeRemoved(KisKeyframeSP)), &dummiesUpdateMapper, SLOT(map())); connect(channel, SIGNAL(sigKeyframeMoved(KisKeyframeSP,int)), &dummiesUpdateMapper, SLOT(map())); dummiesUpdateMapper.setMapping(channel, (QObject*)dummy); } connectionsSet.insert(dummy); } void TimelineNodeListKeeper::Private::disconnectDummy(KisNodeDummy *dummy) { if (!connectionsSet.contains(dummy)) return; QMap channels = dummy->node()->keyframeChannels(); if (channels.isEmpty()) { if (connectionsSet.contains(dummy)) { connectionsSet.remove(dummy); } return; } Q_FOREACH(KisKeyframeChannel *channel, channels) { channel->disconnect(&dummiesUpdateMapper); } connectionsSet.remove(dummy); } void TimelineNodeListKeeper::slotEndInsertDummy(KisNodeDummy *dummy) { KIS_ASSERT_RECOVER_RETURN(!m_d->dummiesList.contains(dummy)); if (m_d->converter.isDummyVisible(dummy)) { int pos = m_d->converter.rowForDummy(dummy); m_d->model->callBeginInsertRows(QModelIndex(), pos, pos); m_d->dummiesList.insert(pos, 1, dummy); m_d->tryConnectDummy(dummy); m_d->model->callEndInsertRows(); } } void TimelineNodeListKeeper::slotBeginRemoveDummy(KisNodeDummy *dummy) { if (m_d->dummiesList.contains(dummy)) { int pos = m_d->dummiesList.indexOf(dummy); m_d->model->callBeginRemoveRows(QModelIndex(), pos, pos); m_d->disconnectDummy(dummy); m_d->dummiesList.remove(pos); m_d->model->callEndRemoveRows(); } m_d->converter.notifyDummyRemoved(dummy); } void TimelineNodeListKeeper::slotDummyChanged(KisNodeDummy *dummy) { const bool present = m_d->dummiesList.contains(dummy); const bool shouldBe = m_d->converter.isDummyVisible(dummy); m_d->tryConnectDummy(dummy); if (!present && shouldBe) { slotEndInsertDummy(dummy); } else if (present && !shouldBe) { slotBeginRemoveDummy(dummy); } } -void findOtherLayers(KisNodeDummy *root, - TimelineNodeListKeeper::OtherLayersList *list, - const QString &prefix) +void TimelineNodeListKeeper::slotDisplayModeChanged() +{ + if (m_d->showGlobalSelectionMask != m_d->displayModeAdapter->showGlobalSelectionMask()) { + + m_d->model->callBeginResetModel(); + + Q_FOREACH (KisNodeDummy *dummy, m_d->dummiesList) { + m_d->disconnectDummy(dummy); + } + m_d->dummiesList.clear(); + + m_d->showGlobalSelectionMask = m_d->displayModeAdapter->showGlobalSelectionMask(); + m_d->converter.setShowGlobalSelectionMask(m_d->showGlobalSelectionMask); + + m_d->populateDummiesList(); + + m_d->model->callEndResetModel(); + } +} + +void TimelineNodeListKeeper::Private::findOtherLayers(KisNodeDummy *root, + TimelineNodeListKeeper::OtherLayersList *list, + const QString &prefix) { KisNodeSP node = root->node(); - if (root->parent() && !node->useInTimeline()) { + if (converter.isDummyAvailableForTimeline(root) && + !root->node()->useInTimeline()) { + *list << TimelineNodeListKeeper::OtherLayer( QString(prefix + node->name()), root); } KisNodeDummy *dummy = root->lastChild(); while(dummy) { findOtherLayers(dummy, list, prefix + " "); dummy = dummy->prevSibling(); } } TimelineNodeListKeeper::OtherLayersList TimelineNodeListKeeper::otherLayersList() const { OtherLayersList list; - findOtherLayers(m_d->dummiesFacade->rootDummy(), &list, ""); + m_d->findOtherLayers(m_d->dummiesFacade->rootDummy(), &list, ""); return list; } diff --git a/plugins/dockers/animation/timeline_node_list_keeper.h b/plugins/dockers/animation/timeline_node_list_keeper.h index 0182aecefc..abddf4eaa9 100644 --- a/plugins/dockers/animation/timeline_node_list_keeper.h +++ b/plugins/dockers/animation/timeline_node_list_keeper.h @@ -1,107 +1,120 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_NODE_LIST_KEEPER_H #define __TIMELINE_NODE_LIST_KEEPER_H #include #include #include #include #include "kritaanimationdocker_export.h" #include "kis_time_based_item_model.h" class KisNodeDummy; class KisDummiesFacadeBase; +class KisNodeDisplayModeAdapter; class KRITAANIMATIONDOCKER_EXPORT TimelineNodeListKeeper : public QObject { Q_OBJECT public: struct ModelWithExternalNotifications; struct OtherLayer; typedef QList OtherLayersList; - TimelineNodeListKeeper(ModelWithExternalNotifications *model, KisDummiesFacadeBase *dummiesFacade); + TimelineNodeListKeeper(ModelWithExternalNotifications *model, + KisDummiesFacadeBase *dummiesFacade, + KisNodeDisplayModeAdapter *displayModeAdapter); ~TimelineNodeListKeeper() override; KisNodeDummy* dummyFromRow(int row); int rowForDummy(KisNodeDummy *dummy); int rowCount(); OtherLayersList otherLayersList() const; void updateActiveDummy(KisNodeDummy *dummy); private Q_SLOTS: void slotEndInsertDummy(KisNodeDummy *dummy); void slotBeginRemoveDummy(KisNodeDummy *dummy); void slotDummyChanged(KisNodeDummy *dummy); void slotUpdateDummyContent(QObject *dummy); + void slotDisplayModeChanged(); + public: struct ModelWithExternalNotifications : public KisTimeBasedItemModel { ModelWithExternalNotifications(QObject *parent) : KisTimeBasedItemModel(parent) {} + void callBeginResetModel() { + beginResetModel(); + } + + void callEndResetModel() { + endResetModel(); + } + void callBeginInsertRows(const QModelIndex &parent, int first, int last) { beginInsertRows(parent, first, last); } void callEndInsertRows() { endInsertRows(); } void callBeginRemoveRows(const QModelIndex &parent, int first, int last) { beginRemoveRows(parent, first, last); } void callEndRemoveRows() { endRemoveRows(); } void callIndexChanged(const QModelIndex &index0, const QModelIndex &index1) { emit dataChanged(index0, index1); } }; struct OtherLayer { OtherLayer(const QString &_name, KisNodeDummy *_dummy) : name(_name), dummy(_dummy) { } QString name; KisNodeDummy *dummy; }; private: struct Private; const QScopedPointer m_d; }; Q_DECLARE_METATYPE( TimelineNodeListKeeper::OtherLayersList ) #endif /* __TIMELINE_NODE_LIST_KEEPER_H */