[krita] krita: Implement Move Up/Down actions for multiple layer selections

Dmitry Kazakov dimula73 at gmail.com
Mon Dec 28 17:17:44 UTC 2015


Git commit 8170a9252f3e9cfee38c7ef98779379a65015442 by Dmitry Kazakov.
Committed on 28/12/2015 at 17:17.
Pushed by dkazakov into branch 'master'.

Implement Move Up/Down actions for multiple layer selections

Now one cane move up/down layers and:

1) The moves are compressed with the delay of 1 sec
2) The layers are automatically enter/leave groups which stay on their way.
3) The selection of the layers is kept unchanged after the move

CC:kimageshop at kde.org

M  +3    -0    krita/image/kis_layer_utils.h
M  +8    -1    krita/image/kis_node.cpp
M  +2    -1    krita/image/kis_types.h
M  +11   -0    krita/image/krita_utils.h
M  +51   -28   krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
M  +3    -0    krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.h
M  +189  -27   krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.cpp
M  +5    -3    krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.h
M  +1    -1    krita/plugins/extensions/dockers/defaultdockers/tests/kis_node_juggler_compressed_test.cpp
M  +2    -7    krita/sketch/models/LayerModel.cpp
M  +1    -0    krita/ui/CMakeLists.txt
M  +10   -5    krita/ui/KisNodeView.cpp
M  +33   -10   krita/ui/kis_node_manager.cpp
M  +14   -8    krita/ui/kis_node_manager.h
M  +23   -7    krita/ui/kis_node_model.cpp
M  +2    -1    krita/ui/kis_node_model.h
A  +47   -0    krita/ui/kis_node_selection_adapter.cpp     [License: GPL (v2+)]
C  +11   -28   krita/ui/kis_node_selection_adapter.h [from: krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.h - 054% similarity]
M  +2    -2    krita/ui/tests/kis_model_index_converter_test.cpp
M  +5    -5    krita/ui/tests/kis_node_model_test.cpp

http://commits.kde.org/krita/8170a9252f3e9cfee38c7ef98779379a65015442

diff --git a/krita/image/kis_layer_utils.h b/krita/image/kis_layer_utils.h
index 4463ab3..384d3fa 100644
--- a/krita/image/kis_layer_utils.h
+++ b/krita/image/kis_layer_utils.h
@@ -30,6 +30,9 @@ namespace KisMetaData
 
 namespace KisLayerUtils
 {
+    KRITAIMAGE_EXPORT void sortMergableNodes(KisNodeSP root, QList<KisNodeSP> &inputNodes, QList<KisNodeSP> &outputNodes);
+    KRITAIMAGE_EXPORT void filterMergableNodes(QList<KisNodeSP> &nodes);
+
     KRITAIMAGE_EXPORT void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy);
 
     KRITAIMAGE_EXPORT QSet<int> fetchLayerFrames(KisNodeSP node);
diff --git a/krita/image/kis_node.cpp b/krita/image/kis_node.cpp
index ec4fd96..dfdb51b 100644
--- a/krita/image/kis_node.cpp
+++ b/krita/image/kis_node.cpp
@@ -56,7 +56,14 @@ struct KisNodeSPStaticRegistrar {
         qRegisterMetaType<KisNodeSP>("KisNodeSP");
     }
 };
-static KisNodeSPStaticRegistrar __registrar;
+static KisNodeSPStaticRegistrar __registrar1;
+
+struct KisNodeListStaticRegistrar {
+    KisNodeListStaticRegistrar() {
+        qRegisterMetaType<KisNodeList>("KisNodeList");
+    }
+};
+static KisNodeListStaticRegistrar __registrar2;
 
 
 /**
diff --git a/krita/image/kis_types.h b/krita/image/kis_types.h
index 1cdcb7a..1b1f977 100644
--- a/krita/image/kis_types.h
+++ b/krita/image/kis_types.h
@@ -222,7 +222,8 @@ typedef KisSharedPtr<KisProcessingVisitor> KisProcessingVisitorSP;
 class KUndo2Command;
 typedef QSharedPointer<KUndo2Command> KUndo2CommandSP;
 
-typedef QSharedPointer<QList <KisNodeSP> > KisNodeListSP;
+typedef QList<KisNodeSP> KisNodeList;
+typedef QSharedPointer<KisNodeList> KisNodeListSP;
 
 class KisStroke;
 typedef QSharedPointer<KisStroke> KisStrokeSP;
diff --git a/krita/image/krita_utils.h b/krita/image/krita_utils.h
index 567bc77..6d54fe4 100644
--- a/krita/image/krita_utils.h
+++ b/krita/image/krita_utils.h
@@ -69,6 +69,17 @@ namespace KritaUtils
     QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value);
 
     KisNodeSP KRITAIMAGE_EXPORT nearestNodeAfterRemoval(KisNodeSP node);
+
+    template <class T>
+        bool compareListsUnordered(const QList<T> &a, const QList<T> &b) {
+        if (a.size() != b.size()) return false;
+
+        Q_FOREACH(const T &t, a) {
+            if (!b.contains(t)) return false;
+        }
+
+        return true;
+    }
 }
 
 #endif /* __KRITA_UTILS_H */
diff --git a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
index 31650a1..e0964f8 100644
--- a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
+++ b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
@@ -76,6 +76,7 @@
 #include "kis_config.h"
 #include "KisView.h"
 #include "kis_node_juggler_compressed.h"
+#include "krita_utils.h"
 
 #include "ui_wdglayerbox.h"
 
@@ -122,6 +123,7 @@ KisLayerBox::KisLayerBox()
         : QDockWidget(i18n("Layers"))
         , m_canvas(0)
         , m_wdgLayerBox(new Ui_WdgLayerBox)
+        , m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE)
 {
     KisConfig cfg;
 
@@ -131,11 +133,6 @@ KisLayerBox::KisLayerBox()
 
     m_wdgLayerBox->setupUi(mainWidget);
 
-    m_wdgLayerBox->listLayers->setDefaultDropAction(Qt::MoveAction);
-    m_wdgLayerBox->listLayers->setSelectionMode(QAbstractItemView::ExtendedSelection);
-    m_wdgLayerBox->listLayers->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
-    m_wdgLayerBox->listLayers->setSelectionBehavior(QAbstractItemView::SelectRows);
-
     connect(m_wdgLayerBox->listLayers,
             SIGNAL(contextMenuRequested(const QPoint&, const QModelIndex&)),
             this, SLOT(slotContextMenuRequested(const QPoint&, const QModelIndex&)));
@@ -269,6 +266,8 @@ KisLayerBox::KisLayerBox()
     m_wdgLayerBox->listLayers->setModel(m_nodeModel);
 
     setEnabled(false);
+
+    connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail()));
 }
 
 KisLayerBox::~KisLayerBox()
@@ -305,7 +304,6 @@ void KisLayerBox::setMainWindow(KisViewManager* kisview)
 {
     m_nodeManager = kisview->nodeManager();
 
-
     Q_FOREACH (KisAction *action, m_actions) {
         kisview->actionManager()->
             addAction(action->objectName(),
@@ -325,13 +323,13 @@ void KisLayerBox::setCanvas(KoCanvasBase *canvas)
 
     if (m_canvas) {
         m_canvas->disconnectCanvasObserver(this);
-        m_nodeModel->setDummiesFacade(0, 0, 0);
+        m_nodeModel->setDummiesFacade(0, 0, 0, 0);
 
         disconnect(m_image, 0, this, 0);
         disconnect(m_nodeManager, 0, this, 0);
         disconnect(m_nodeModel, 0, m_nodeManager, 0);
         disconnect(m_nodeModel, SIGNAL(nodeActivated(KisNodeSP)), this, SLOT(updateUI()));
-        m_nodeManager->setSelectedNodes(QList<KisNodeSP>());
+        m_nodeManager->slotSetSelectedNodes(KisNodeList());
     }
 
     m_canvas = dynamic_cast<KisCanvas2*>(canvas);
@@ -339,14 +337,14 @@ void KisLayerBox::setCanvas(KoCanvasBase *canvas)
     if (m_canvas) {
         m_image = m_canvas->image();
 
-        connect(m_image, SIGNAL(sigImageUpdated(QRect)), SLOT(updateThumbnail()));
+        connect(m_image, SIGNAL(sigImageUpdated(QRect)), &m_thumbnailCompressor, SLOT(start()));
 
         KisDocument* doc = static_cast<KisDocument*>(m_canvas->imageView()->document());
         KisShapeController *kritaShapeController =
             dynamic_cast<KisShapeController*>(doc->shapeController());
         KisDummiesFacadeBase *kritaDummiesFacade =
             static_cast<KisDummiesFacadeBase*>(kritaShapeController);
-        m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController);
+        m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController, m_nodeManager->nodeSelectionAdapter());
 
         connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted()));
         connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged()));
@@ -363,11 +361,9 @@ void KisLayerBox::setCanvas(KoCanvasBase *canvas)
         connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)),
                 this, SLOT(setCurrentNode(KisNodeSP)));
 
-        // Connection KisLayerBox -> KisNodeManager
-        // The order of these connections is important! See comment in the ctor
-        connect(m_nodeModel, SIGNAL(nodeActivated(KisNodeSP)),
-                m_nodeManager, SLOT(slotUiActivatedNode(KisNodeSP)));
-        connect(m_nodeModel, SIGNAL(nodeActivated(KisNodeSP)), SLOT(updateUI()));
+        connect(m_nodeManager,
+                SIGNAL(sigUiNeedChangeSelectedNodes(const QList<KisNodeSP> &)),
+                SLOT(slotNodeManagerChangedSelection(const QList<KisNodeSP> &)));
 
         // Connection KisLayerBox -> KisNodeManager (isolate layer)
         connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()),
@@ -407,12 +403,12 @@ void KisLayerBox::unsetCanvas()
         m_newLayerMenu->clear();
     }
 
-    m_nodeModel->setDummiesFacade(0, 0, 0);
+    m_nodeModel->setDummiesFacade(0, 0, 0, 0);
     disconnect(m_image, 0, this, 0);
     disconnect(m_nodeManager, 0, this, 0);
     disconnect(m_nodeModel, 0, m_nodeManager, 0);
     disconnect(m_nodeModel, SIGNAL(nodeActivated(KisNodeSP)), this, SLOT(updateUI()));
-    m_nodeManager->setSelectedNodes(QList<KisNodeSP>());
+    m_nodeManager->slotSetSelectedNodes(KisNodeList());
 
     m_canvas = 0;
 }
@@ -478,7 +474,7 @@ void KisLayerBox::setCurrentNode(KisNodeSP node)
 {
     QModelIndex index = node ? m_nodeModel->indexFromNode(node) : QModelIndex();
 
-    m_wdgLayerBox->listLayers->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
+    m_nodeModel->setData(index, true, KisNodeModel::ActiveRole);
     updateUI();
 }
 
@@ -606,11 +602,11 @@ void KisLayerBox::slotRaiseClicked()
     if (!m_canvas) return;
 
     if (!m_nodeJuggler) {
-        m_nodeJuggler = new KisNodeJugglerCompressed(kundo2_i18n("Move Nodes"), m_image, 3000);
+        m_nodeJuggler = new KisNodeJugglerCompressed(kundo2_i18n("Move Nodes"), m_image, m_nodeManager, 1000);
         m_nodeJuggler->setAutoDelete(true);
     }
 
-    m_nodeJuggler->raiseNode(m_nodeManager->activeNode());
+    m_nodeJuggler->raiseNode(m_nodeManager->selectedNodes());
 }
 
 void KisLayerBox::slotLowerClicked()
@@ -618,11 +614,11 @@ void KisLayerBox::slotLowerClicked()
     if (!m_canvas) return;
 
     if (!m_nodeJuggler) {
-        m_nodeJuggler = new KisNodeJugglerCompressed(kundo2_i18n("Move Nodes"), m_image, 3000);
+        m_nodeJuggler = new KisNodeJugglerCompressed(kundo2_i18n("Move Nodes"), m_image, m_nodeManager, 1000);
         m_nodeJuggler->setAutoDelete(true);
     }
 
-    m_nodeJuggler->lowerNode(m_nodeManager->activeNode());
+    m_nodeJuggler->lowerNode(m_nodeManager->selectedNodes());
 }
 
 void KisLayerBox::slotPropertiesClicked()
@@ -754,25 +750,52 @@ void KisLayerBox::selectionChanged(const QModelIndexList selection)
 {
     if (!m_nodeManager) return;
 
+    /**
+     * When the user clears the extended selection by clicking on the
+     * empty area of the docker, the selection should be reset on to
+     * the active layer, which might be even unselected(!).
+     */
     if (selection.isEmpty() && m_nodeManager->activeNode()) {
+        QModelIndex selectedIndex =
+            m_nodeModel->indexFromNode(m_nodeManager->activeNode());
+
         m_wdgLayerBox->listLayers->selectionModel()->
-            setCurrentIndex(
-                m_nodeModel->indexFromNode(m_nodeManager->activeNode()),
-                QItemSelectionModel::ClearAndSelect);
+            setCurrentIndex(selectedIndex, QItemSelectionModel::ClearAndSelect);
         return;
     }
 
-
     QList<KisNodeSP> selectedNodes;
     Q_FOREACH (const QModelIndex &idx, selection) {
         selectedNodes << m_nodeModel->nodeFromIndex(idx);
     }
 
-
-    m_nodeManager->setSelectedNodes(selectedNodes);
+    m_nodeManager->slotSetSelectedNodes(selectedNodes);
     updateUI();
 }
 
+void KisLayerBox::slotNodeManagerChangedSelection(const KisNodeList &nodes)
+{
+    if (!m_nodeManager) return;
+
+    QModelIndexList newSelection;
+    Q_FOREACH(KisNodeSP node, nodes) {
+        newSelection << m_nodeModel->indexFromNode(node);
+    }
+
+    QItemSelectionModel *model = m_wdgLayerBox->listLayers->selectionModel();
+
+    if (KritaUtils::compareListsUnordered(newSelection, model->selectedIndexes())) {
+        return;
+    }
+
+    QItemSelection selection;
+    Q_FOREACH(const QModelIndex &idx, newSelection) {
+        selection.select(idx, idx);
+    }
+
+    model->select(selection, QItemSelectionModel::ClearAndSelect);
+}
+
 void KisLayerBox::updateThumbnail()
 {
     m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex());
diff --git a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.h b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.h
index d921216..5be6448 100644
--- a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.h
+++ b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.h
@@ -39,6 +39,7 @@
 #include "kis_action.h"
 #include "KisViewManager.h"
 #include "kis_mainwindow_observer.h"
+#include "kis_signal_compressor.h"
 
 class QModelIndex;
 
@@ -112,6 +113,7 @@ private Q_SLOTS:
     void slotEditGlobalSelection(bool showSelections);
 
     void selectionChanged(const QModelIndexList selection);
+    void slotNodeManagerChangedSelection(const QList<KisNodeSP> &nodes);
 
     void updateThumbnail();
 
@@ -137,6 +139,7 @@ private:
     KisAction* m_propertiesAction;
     KisAction* m_selectOpaque;
     QPointer<KisNodeJugglerCompressed> m_nodeJuggler;
+    KisSignalCompressor m_thumbnailCompressor;
 };
 
 class KisLayerBoxFactory : public KoDockFactoryBase
diff --git a/krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.cpp b/krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.cpp
index 0b1feed..1f45ace 100644
--- a/krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.cpp
+++ b/krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.cpp
@@ -28,8 +28,22 @@
 #include "commands/kis_image_layer_move_command.h"
 #include "kis_signal_compressor.h"
 #include "kis_command_utils.h"
+#include "kis_layer_utils.h"
+#include "kis_node_manager.h"
 
 
+/**
+ * A special structure that stores information about a node that was
+ * moved. The purpose of the object is twofold:
+ *
+ *     1) When the reordering stroke is already started than the
+ *        parent and sibling nodes may be not consistent anymore. So
+ *        we store it separately.
+ *
+ *     2) This objects allows merging (compressing) multiple moves of
+ *        a layer into a signle action. This behavior is implemented
+ *        in tryMerge() method.
+ */
 struct MoveNodeStruct {
     MoveNodeStruct(KisImageSP _image, KisNodeSP _node, KisNodeSP _parent, KisNodeSP _above)
         : image(_image),
@@ -93,13 +107,26 @@ struct MoveNodeStruct {
 typedef QSharedPointer<MoveNodeStruct> MoveNodeStructSP;
 typedef QHash<KisNodeSP, MoveNodeStructSP> MovedNodesHash;
 
-struct BatchMoveUpdateData {
+
+/**
+ * All the commands executed bythe stroke system are running in the
+ * background asynchronously. But, at the same time, they emit updates
+ * in parallel to the ones emitted by the juggler. Therefore, the
+ * juggler and all its commands should share some data: which updates
+ * have been requested, but not yet dispatched (m_movedNodesInitial),
+ * and what updates have already been processed and executed
+ * (m_movedNodesUpdated). This object is shared via a shared pointer
+ * and guarantees safe (including thread-safe) access to the shared
+ * data.
+ */
+class BatchMoveUpdateData {
     MovedNodesHash m_movedNodesInitial;
     MovedNodesHash m_movedNodesUpdated;
 
-    bool m_jugglerAlive;
     QMutex m_mutex;
 
+private:
+
     static void addToHashLazy(MovedNodesHash *hash, MoveNodeStructSP moveStruct) {
         if (hash->contains(moveStruct->node)) {
             bool result = hash->value(moveStruct->node)->tryMerge(*moveStruct);
@@ -109,6 +136,8 @@ struct BatchMoveUpdateData {
         }
     }
 
+public:
+
     void processUnhandledUpdates() {
         QMutexLocker l(&m_mutex);
 
@@ -131,6 +160,8 @@ struct BatchMoveUpdateData {
     }
 
     void emitFinalUpdates(bool undo) {
+        QMutexLocker l(&m_mutex);
+
         if (m_movedNodesUpdated.isEmpty()) return;
 
         MovedNodesHash::const_iterator it = m_movedNodesUpdated.constBegin();
@@ -148,6 +179,10 @@ struct BatchMoveUpdateData {
 
 typedef QSharedPointer<BatchMoveUpdateData> BatchMoveUpdateDataSP;
 
+/**
+ * A command that emits a update signals on the compressed move undo
+ * or redo.
+ */
 class UpdateMovedNodesCommand : public KisCommandUtils::FlipFlopCommand
 {
 public:
@@ -158,36 +193,131 @@ public:
     }
 
     void end() {
-        if (isFirstRedo()) {
+        if (isFinalizing() && isFirstRedo()) {
+            /**
+             * When doing the first redo() some of the updates might
+             * have already been executed by the juggler itself, so we
+             * should process'unhandled' updates only
+             */
             m_updateData->processUnhandledUpdates();
         } else {
+            /**
+             * When being executed by real undo/redo operations, we
+             * should emit all the update signals. Noone else will do
+             * that for us (juggler, which did it in the previous
+             * case, might have already died).
+             */
             m_updateData->emitFinalUpdates(isFinalizing());
         }
     }
 private:
     BatchMoveUpdateDataSP m_updateData;
-    bool m_firstRun;
 };
 
+/**
+ * A special QObject-based proxy object that connects to the node
+ * manager and allows the command to emit signals to change
+ * active/selected nodes from another (non-gui) thread.
+ */
+class SelectionSignalsProxy : public QObject {
+    Q_OBJECT
+public:
+    SelectionSignalsProxy(KisNodeManager *nodeManager, QObject *parent = 0)
+        : QObject(parent)
+    {
+        connect(this, SIGNAL(sigActivateNode(KisNodeSP)), nodeManager, SLOT(slotNonUiActivatedNode(KisNodeSP)));
+        connect(this, SIGNAL(sigSetSelectedNodes(const KisNodeList&)), nodeManager, SLOT(slotSetSelectedNodes(const KisNodeList&)));
+    }
+
+    void changeLayersSelection(KisNodeSP activeNode, const KisNodeList &selectedNodes) {
+        emit sigActivateNode(activeNode);
+        emit sigSetSelectedNodes(selectedNodes);
+    }
+
+Q_SIGNALS:
+    void sigActivateNode(KisNodeSP node);
+    void sigSetSelectedNodes(const KisNodeList &nodes);
+};
+
+/**
+ * A command to keep correct set of selected/active nodes thoroughout
+ * the action.
+ */
+class KeepNodesSelectedCommand : public KisCommandUtils::FlipFlopCommand
+{
+public:
+    KeepNodesSelectedCommand(const KisNodeList &selectedBefore,
+                             const KisNodeList &selectedAfter,
+                             KisNodeSP activeBefore,
+                             KisNodeSP activeAfter,
+                             KisNodeManager *nodeManager,
+                             bool finalize, KUndo2Command *parent = 0)
+        : FlipFlopCommand(finalize, parent),
+          m_selectedBefore(selectedBefore),
+          m_selectedAfter(selectedAfter),
+          m_activeBefore(activeBefore),
+          m_activeAfter(activeAfter),
+          m_nodeManager(nodeManager),
+          m_signalProxy(new SelectionSignalsProxy(nodeManager))
+    {
+    }
+
+    void end() {
+        if (isFinalizing()) {
+            m_signalProxy->changeLayersSelection(m_activeAfter, m_selectedAfter);
+        } else {
+            m_signalProxy->changeLayersSelection(m_activeBefore, m_selectedBefore);
+        }
+    }
+
+private:
+    KisNodeList m_selectedBefore;
+    KisNodeList m_selectedAfter;
+    KisNodeSP m_activeBefore;
+    KisNodeSP m_activeAfter;
+    KisNodeManager *m_nodeManager;
+    QScopedPointer<SelectionSignalsProxy> m_signalProxy;
+};
+
+/**
+ * A generalized command to muve up/down a set of layer
+ */
 struct LowerRaiseLayer : public KisCommandUtils::AggregateCommand {
     LowerRaiseLayer(BatchMoveUpdateDataSP updateData,
-               KisImageSP image,
-               KisNodeSP node,
-               bool lower)
+                    KisNodeManager *nodeManager,
+                    KisImageSP image,
+                    const KisNodeList &nodes,
+                    KisNodeSP activeNode,
+                    bool lower)
         : m_updateData(updateData),
+          m_nodeManager(nodeManager),
           m_image(image),
-          m_node(node),
+          m_nodes(nodes),
+          m_activeNode(activeNode),
           m_lower (lower) {}
 
+    KisNodeList sortAndFilterNodes(const KisNodeList &nodes) {
+        KisNodeList filteredNodes = nodes;
+        KisNodeList sortedNodes;
+
+        KisLayerUtils::filterMergableNodes(filteredNodes);
+        KisLayerUtils::sortMergableNodes(m_image->root(), filteredNodes, sortedNodes);
+
+        return sortedNodes;
+    }
+
     void populateChildCommands() {
-        KisNodeSP parent = m_node->parent();
+        KisNodeList sortedNodes = sortAndFilterNodes(m_nodes);
+        KisNodeSP headNode = m_lower ? sortedNodes.first() : sortedNodes.last();
+
+        KisNodeSP parent = headNode->parent();
         KisNodeSP grandParent = parent ? parent->parent() : 0;
 
         KisNodeSP newAbove;
         KisNodeSP newParent;
 
         if (m_lower) {
-            KisNodeSP prevNode = m_node->prevSibling();
+            KisNodeSP prevNode = headNode->prevSibling();
 
             if (prevNode) {
                 if (prevNode->inherits("KisGroupLayer") &&
@@ -200,13 +330,13 @@ struct LowerRaiseLayer : public KisCommandUtils::AggregateCommand {
                     newParent = parent;
                 }
             } else if (grandParent &&
-                       !m_node->inherits("KisMask")) {
+                       !headNode->inherits("KisMask")) { // TODO
 
                 newAbove = parent->prevSibling();
                 newParent = grandParent;
             }
         } else {
-            KisNodeSP nextNode = m_node->nextSibling();
+            KisNodeSP nextNode = headNode->nextSibling();
 
             if (nextNode) {
                 if (nextNode->inherits("KisGroupLayer") &&
@@ -219,7 +349,7 @@ struct LowerRaiseLayer : public KisCommandUtils::AggregateCommand {
                     newParent = parent;
                 }
             } else if (grandParent &&
-                       !m_node->inherits("KisMask")) {
+                       !headNode->inherits("KisMask")) { // TODO
 
                 newAbove = parent;
                 newParent = grandParent;
@@ -228,23 +358,37 @@ struct LowerRaiseLayer : public KisCommandUtils::AggregateCommand {
 
         if (!newParent) return;
 
-        MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, m_node, newParent, newAbove));
-        addCommand(new KisImageLayerMoveCommand(m_image, m_node, newParent, newAbove, false));
-        m_updateData->addInitialUpdate(moveStruct);
+        addCommand(new KeepNodesSelectedCommand(sortedNodes, sortedNodes,
+                                                m_activeNode, m_activeNode,
+                                                m_nodeManager, false));
+
+        KisNodeSP currentAbove = newAbove;
+        Q_FOREACH (KisNodeSP node, sortedNodes) {
+            MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, node, newParent, currentAbove));
+            addCommand(new KisImageLayerMoveCommand(m_image, node, newParent, currentAbove, false));
+            m_updateData->addInitialUpdate(moveStruct);
+            currentAbove = node;
+        }
+
+        addCommand(new KeepNodesSelectedCommand(sortedNodes, sortedNodes,
+                                                m_activeNode, m_activeNode,
+                                                m_nodeManager, true));
     }
 
 private:
     BatchMoveUpdateDataSP m_updateData;
-
+    KisNodeManager *m_nodeManager;
     KisImageSP m_image;
-    KisNodeSP m_node;
+    KisNodeList m_nodes;
+    KisNodeSP m_activeNode;
     bool m_lower;
 };
 
 struct KisNodeJugglerCompressed::Private
 {
-    Private(KisImageSP _image, int _timeout)
+    Private(KisImageSP _image, KisNodeManager *_nodeManager, int _timeout)
         : image(_image),
+          nodeManager(_nodeManager),
           compressor(_timeout, KisSignalCompressor::POSTPONE),
           selfDestructionCompressor(3 * _timeout, KisSignalCompressor::POSTPONE),
           updateData(new BatchMoveUpdateData),
@@ -253,6 +397,7 @@ struct KisNodeJugglerCompressed::Private
     {}
 
     KisImageSP image;
+    KisNodeManager *nodeManager;
     QScopedPointer<KisProcessingApplicator> applicator;
 
     KisSignalCompressor compressor;
@@ -264,8 +409,8 @@ struct KisNodeJugglerCompressed::Private
     bool isStarted;
 };
 
-KisNodeJugglerCompressed::KisNodeJugglerCompressed(const KUndo2MagicString &actionName, KisImageSP image, int timeout)
-    : m_d(new Private(image, timeout))
+KisNodeJugglerCompressed::KisNodeJugglerCompressed(const KUndo2MagicString &actionName, KisImageSP image, KisNodeManager *nodeManager, int timeout)
+    : m_d(new Private(image, nodeManager, timeout))
 {
     connect(m_d->image, SIGNAL(sigStrokeCancellationRequested()), SLOT(slotEndStrokeRequested()));
     connect(m_d->image, SIGNAL(sigStrokeEndRequestedActiveNodeFiltered()), SLOT(slotEndStrokeRequested()));
@@ -281,7 +426,7 @@ KisNodeJugglerCompressed::KisNodeJugglerCompressed(const KUndo2MagicString &acti
     connect(&m_d->compressor, SIGNAL(timeout()), SLOT(slotUpdateTimeout()));
 
     m_d->applicator->applyCommand(
-            new UpdateMovedNodesCommand(m_d->updateData, false));
+        new UpdateMovedNodesCommand(m_d->updateData, false));
     m_d->isStarted = true;
 }
 
@@ -289,18 +434,33 @@ KisNodeJugglerCompressed::~KisNodeJugglerCompressed()
 {
 }
 
-void KisNodeJugglerCompressed::lowerNode(KisNodeSP node)
+KisNodeList KisNodeJugglerCompressed::sortAndFilterNodes(const KisNodeList &nodes)
+{
+    KisNodeList filteredNodes = nodes;
+    KisNodeList sortedNodes;
+
+    KisLayerUtils::filterMergableNodes(filteredNodes);
+    KisLayerUtils::sortMergableNodes(m_d->image->root(), filteredNodes, sortedNodes);
+
+    return sortedNodes;
+}
+
+void KisNodeJugglerCompressed::lowerNode(const KisNodeList &nodes)
 {
     m_d->applicator->applyCommand(
-        new LowerRaiseLayer(m_d->updateData, m_d->image, node, true));
+        new LowerRaiseLayer(m_d->updateData, m_d->nodeManager,
+                            m_d->image,
+                            nodes, m_d->nodeManager->activeNode(), true));
 
     startTimers();
 }
 
-void KisNodeJugglerCompressed::raiseNode(KisNodeSP node)
+void KisNodeJugglerCompressed::raiseNode(const KisNodeList &nodes)
 {
     m_d->applicator->applyCommand(
-        new LowerRaiseLayer(m_d->updateData, m_d->image, node, false));
+        new LowerRaiseLayer(m_d->updateData, m_d->nodeManager,
+                            m_d->image,
+                            nodes, m_d->nodeManager->activeNode(), false));
     startTimers();
 }
 
@@ -333,7 +493,7 @@ void KisNodeJugglerCompressed::end()
     if (!m_d->isStarted) return;
 
     m_d->applicator->applyCommand(
-            new UpdateMovedNodesCommand(m_d->updateData, true));
+        new UpdateMovedNodesCommand(m_d->updateData, true));
 
     m_d->applicator->end();
     m_d->applicator.reset();
@@ -362,3 +522,5 @@ bool KisNodeJugglerCompressed::isEnded() const
 {
     return !m_d->isStarted;
 }
+
+#include "kis_node_juggler_compressed.moc"
diff --git a/krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.h b/krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.h
index b4ecb2f..0dacbb3 100644
--- a/krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.h
+++ b/krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.h
@@ -25,13 +25,14 @@
 #include <kritadefaultdockers_export.h>
 #include <kundo2command.h>
 #include "kis_types.h"
+#include "kis_node_manager.h"
 
 
 class KRITADEFAULTDOCKERS_EXPORT KisNodeJugglerCompressed : public QObject
 {
     Q_OBJECT
 public:
-    KisNodeJugglerCompressed(const KUndo2MagicString &actionName, KisImageSP image, int timeout);
+    KisNodeJugglerCompressed(const KUndo2MagicString &actionName, KisImageSP image, KisNodeManager *nodeManager, int timeout);
     ~KisNodeJugglerCompressed();
 
     void moveNode(KisNodeSP node, KisNodeSP parent, KisNodeSP above);
@@ -39,8 +40,8 @@ public:
 
     bool isEnded() const;
 
-    void lowerNode(KisNodeSP node);
-    void raiseNode(KisNodeSP node);
+    void lowerNode(const KisNodeList &nodes);
+    void raiseNode(const KisNodeList &nodes);
 
 public Q_SLOTS:
     void end();
@@ -51,6 +52,7 @@ private Q_SLOTS:
 
 private:
     void startTimers();
+    KisNodeList sortAndFilterNodes(const KisNodeList &nodes);
 
 private:
     struct Private;
diff --git a/krita/plugins/extensions/dockers/defaultdockers/tests/kis_node_juggler_compressed_test.cpp b/krita/plugins/extensions/dockers/defaultdockers/tests/kis_node_juggler_compressed_test.cpp
index 3202e6c..ddba0c4 100644
--- a/krita/plugins/extensions/dockers/defaultdockers/tests/kis_node_juggler_compressed_test.cpp
+++ b/krita/plugins/extensions/dockers/defaultdockers/tests/kis_node_juggler_compressed_test.cpp
@@ -55,7 +55,7 @@ void KisNodeJugglerCompressedTest::testMove(int delayBeforeEnd)
     TestUtil::ExternalImageChecker chk("node_juggler", "move_test");
     chk.setMaxFailingPixels(0);
 
-    KisNodeJugglerCompressed juggler(kundo2_i18n("Move Layer"), p->image, 600);
+    KisNodeJugglerCompressed juggler(kundo2_i18n("Move Layer"), p->image, 0, 600);
     QVERIFY(chk.checkImage(p->image, "initial"));
 
     juggler.moveNode(layer1, p->image->root(), layer2);
diff --git a/krita/sketch/models/LayerModel.cpp b/krita/sketch/models/LayerModel.cpp
index 2afb2b4..78b969e 100644
--- a/krita/sketch/models/LayerModel.cpp
+++ b/krita/sketch/models/LayerModel.cpp
@@ -261,7 +261,7 @@ void LayerModel::setView(QObject *newView)
         d->layers.clear();
         d->activeNode.clear();
         d->canvas = 0;
-        d->nodeModel->setDummiesFacade(0, 0, 0);
+        d->nodeModel->setDummiesFacade(0, 0, 0, 0);
 
     }
 
@@ -283,7 +283,7 @@ void LayerModel::setView(QObject *newView)
 
         KisDummiesFacadeBase *kritaDummiesFacade = dynamic_cast<KisDummiesFacadeBase*>(d->canvas->imageView()->document()->shapeController());
         KisShapeController *shapeController = dynamic_cast<KisShapeController*>(d->canvas->imageView()->document()->shapeController());
-        d->nodeModel->setDummiesFacade(kritaDummiesFacade, d->image, shapeController);
+        d->nodeModel->setDummiesFacade(kritaDummiesFacade, d->image, shapeController, d->nodeManager->nodeSelectionAdapter());
 
         connect(d->image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted()));
         connect(d->image, SIGNAL(sigNodeChanged(KisNodeSP)), SLOT(nodeChanged(KisNodeSP)));
@@ -296,11 +296,6 @@ void LayerModel::setView(QObject *newView)
         // Connection KisNodeManager -> KisLayerBox
         connect(d->nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(currentNodeChanged(KisNodeSP)));
 
-        // Connection KisLayerBox -> KisNodeManager
-        // The order of these connections is important! See comment in the ctor
-        connect(d->nodeModel, SIGNAL(nodeActivated(KisNodeSP)), d->nodeManager, SLOT(slotUiActivatedNode(KisNodeSP)));
-        connect(d->nodeModel, SIGNAL(nodeActivated(KisNodeSP)), SLOT(currentNodeChanged(KisNodeSP)));
-
         // Node manipulation methods are forwarded to the node manager
         connect(d->nodeModel, SIGNAL(requestAddNode(KisNodeSP, KisNodeSP, KisNodeSP)),
                 d->nodeManager, SLOT(addNodeDirect(KisNodeSP, KisNodeSP, KisNodeSP)));
diff --git a/krita/ui/CMakeLists.txt b/krita/ui/CMakeLists.txt
index efc7910..df13163 100644
--- a/krita/ui/CMakeLists.txt
+++ b/krita/ui/CMakeLists.txt
@@ -106,6 +106,7 @@ set(kritaui_LIB_SRCS
     kis_mimedata.cpp
     kis_node_commands_adapter.cpp
     kis_node_manager.cpp
+    kis_node_selection_adapter.cpp
     kis_node_model.cpp
     kis_model_index_converter_base.cpp
     kis_model_index_converter.cpp
diff --git a/krita/ui/KisNodeView.cpp b/krita/ui/KisNodeView.cpp
index f49c3c0..30dbd3c 100644
--- a/krita/ui/KisNodeView.cpp
+++ b/krita/ui/KisNodeView.cpp
@@ -20,6 +20,7 @@
 #include "KisNodePropertyAction_p.h"
 #include "KisNodeDelegate.h"
 #include "kis_node_model.h"
+#include "kis_signals_blocker.h"
 
 
 #include <kconfig.h>
@@ -82,9 +83,11 @@ KisNodeView::KisNodeView(QWidget *parent)
     , d(new Private(this))
 {
     setMouseTracking(true);
-    setVerticalScrollMode(ScrollPerPixel);
-    setSelectionMode(SingleSelection);
     setSelectionBehavior(SelectItems);
+    setDefaultDropAction(Qt::MoveAction);
+    setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
+    setSelectionMode(QAbstractItemView::ExtendedSelection);
+
     header()->hide();
     setDragEnabled(true);
     setDragDropMode(QAbstractItemView::DragDrop);
@@ -229,8 +232,11 @@ void KisNodeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bot
     QTreeView::dataChanged(topLeft, bottomRight);
     for (int x = topLeft.row(); x <= bottomRight.row(); ++x) {
         for (int y = topLeft.column(); y <= bottomRight.column(); ++y) {
-            if (topLeft.sibling(x, y).data(Model::ActiveRole).toBool()) {
-                setCurrentIndex(topLeft.sibling(x, y));
+            QModelIndex index = topLeft.sibling(x, y);
+            if (index.data(Model::ActiveRole).toBool()) {
+                if (currentIndex() != index) {
+                    setCurrentIndex(index);
+                }
                 return;
             }
         }
@@ -241,7 +247,6 @@ void KisNodeView::selectionChanged(const QItemSelection &selected, const QItemSe
 {
     QTreeView::selectionChanged(selected, deselected);
     emit selectionChanged(selectedIndexes());
-
 }
 
 void KisNodeView::slotActionToggled(bool on, const QPersistentModelIndex &index, int num)
diff --git a/krita/ui/kis_node_manager.cpp b/krita/ui/kis_node_manager.cpp
index 803a503..b46ccf5 100644
--- a/krita/ui/kis_node_manager.cpp
+++ b/krita/ui/kis_node_manager.cpp
@@ -66,6 +66,7 @@
 #include "kis_processing_applicator.h"
 #include "kis_sequential_iterator.h"
 #include "kis_transaction.h"
+#include "kis_node_selection_adapter.h"
 
 #include "processing/kis_mirror_processing_visitor.h"
 #include "KisView.h"
@@ -79,6 +80,7 @@ struct KisNodeManager::Private {
         , layerManager(v)
         , maskManager(v)
         , commandsAdapter(v)
+        , nodeSelectionAdapter(new KisNodeSelectionAdapter(q))
     {
     }
 
@@ -88,8 +90,9 @@ struct KisNodeManager::Private {
     KisLayerManager layerManager;
     KisMaskManager maskManager;
     KisNodeCommandsAdapter commandsAdapter;
+    QScopedPointer<KisNodeSelectionAdapter> nodeSelectionAdapter; 
 
-    QList<KisNodeSP> selectedNodes;
+    KisNodeList selectedNodes;
 
     bool activateNodeImpl(KisNodeSP node);
 
@@ -493,8 +496,10 @@ void KisNodeManager::convertNode(const QString &nodeType)
     }
 }
 
-void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node)
+void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node)
 {
+    KIS_ASSERT_RECOVER_RETURN(node != activeNode());
+
     if (m_d->activateNodeImpl(node)) {
         emit sigUiNeedChangeActiveNode(node);
         emit sigNodeActivated(node);
@@ -508,12 +513,24 @@ void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node)
     }
 }
 
-void KisNodeManager::slotUiActivatedNode(KisNodeSP node)
+void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node)
 {
-    if (m_d->activateNodeImpl(node)) {
-        emit sigNodeActivated(node);
-        nodesUpdated();
+    if (node == activeNode()) return;
+    slotSomethingActivatedNodeImpl(node);
+
+    if (node) {
+        bool toggled =  m_d->view->actionCollection()->action("view_show_just_the_canvas")->isChecked();
+        if (toggled) {
+            m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
+        }
     }
+}
+
+void KisNodeManager::slotUiActivatedNode(KisNodeSP node)
+{
+    if (node == activeNode()) return;
+
+    slotSomethingActivatedNodeImpl(node);
 
     if (node) {
         QStringList vectorTools = QStringList()
@@ -606,16 +623,22 @@ void KisNodeManager::setNodeCompositeOp(KisNodeSP node,
     m_d->commandsAdapter.setCompositeOp(node, compositeOp);
 }
 
-void KisNodeManager::setSelectedNodes(QList<KisNodeSP> nodes)
+void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes)
 {
     m_d->selectedNodes = nodes;
+    emit sigUiNeedChangeSelectedNodes(nodes);
 }
 
-QList<KisNodeSP> KisNodeManager::selectedNodes()
+KisNodeList KisNodeManager::selectedNodes()
 {
     return m_d->selectedNodes;
 }
 
+KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const
+{
+    return m_d->nodeSelectionAdapter.data();
+}
+
 void KisNodeManager::nodeOpacityChanged(qreal opacity, bool finalChange)
 {
     KisNodeSP node = activeNode();
@@ -710,7 +733,7 @@ bool scanForLastLayer(KisImageWSP image, KisNodeSP nodeToRemove)
 }
 
 /// Scan whether the node has a parent in the list of nodes
-bool scanForParent(QList<KisNodeSP> nodeList, KisNodeSP node)
+bool scanForParent(KisNodeList nodeList, KisNodeSP node)
 {
     KisNodeSP parent = node->parent();
 
@@ -742,7 +765,7 @@ void KisNodeManager::removeSingleNode(KisNodeSP node)
     }
 }
 
-void KisNodeManager::removeSelectedNodes(QList<KisNodeSP> selectedNodes)
+void KisNodeManager::removeSelectedNodes(KisNodeList selectedNodes)
 {
     m_d->commandsAdapter.beginMacro(kundo2_i18n("Remove Multiple Layers and Masks"));
     Q_FOREACH (KisNodeSP node, selectedNodes) {
diff --git a/krita/ui/kis_node_manager.h b/krita/ui/kis_node_manager.h
index 0c80af5..99f3686 100644
--- a/krita/ui/kis_node_manager.h
+++ b/krita/ui/kis_node_manager.h
@@ -34,6 +34,7 @@ class KisFilterStrategy;
 class KisViewManager;
 class KisActionManager;
 class KisView;
+class KisNodeSelectionAdapter;
 
 /**
  * The node manager passes requests for new layers or masks on to the mask and layer
@@ -63,6 +64,8 @@ Q_SIGNALS:
     /// without telling the node manager that the node is activated,
     /// preventing loops (I think...)
     void sigUiNeedChangeActiveNode(KisNodeSP node);
+
+    void sigUiNeedChangeSelectedNodes(const KisNodeList &nodes);
     
 public:
     
@@ -95,12 +98,9 @@ public:
      */
     void setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp);
 
-    /**
-     * @brief setSelectedNodes set the list of nodes selected in the layerbox. Selected nodes are not necessarily active nodes.
-     * @param nodes the selected nodes
-     */
-    void setSelectedNodes(QList<KisNodeSP> nodes);
-    QList<KisNodeSP> selectedNodes();
+    KisNodeList selectedNodes();
+
+    KisNodeSelectionAdapter* nodeSelectionAdapter() const;
 
 public Q_SLOTS:
 
@@ -201,6 +201,12 @@ public Q_SLOTS:
     void slotSplitAlphaWrite();
     void slotSplitAlphaSaveMerged();
 
+    /**
+     * @brief slotSetSelectedNodes set the list of nodes selected in the layerbox. Selected nodes are not necessarily active nodes.
+     * @param nodes the selected nodes
+     */
+    void slotSetSelectedNodes(const KisNodeList &nodes);
+
 public:
 
     
@@ -217,8 +223,8 @@ private:
      * to the integer range 0...255
      */
     qint32 convertOpacityToInt(qreal opacity);
-    void removeSelectedNodes(QList<KisNodeSP> selectedNodes);
-
+    void removeSelectedNodes(KisNodeList selectedNodes);
+    void slotSomethingActivatedNodeImpl(KisNodeSP node);
 
     struct Private;
     Private * const m_d;
diff --git a/krita/ui/kis_node_model.cpp b/krita/ui/kis_node_model.cpp
index 044bdb6..a06d076 100644
--- a/krita/ui/kis_node_model.cpp
+++ b/krita/ui/kis_node_model.cpp
@@ -45,6 +45,7 @@
 #include "kis_node_dummies_graph.h"
 #include "kis_model_index_converter.h"
 #include "kis_model_index_converter_show_all.h"
+#include "kis_node_selection_adapter.h"
 
 #include "kis_config.h"
 #include "kis_config_notifier.h"
@@ -55,15 +56,17 @@ struct KisNodeModel::Private
 {
     KisImageWSP image;
     KisShapeController *shapeController = 0;
+    KisNodeSelectionAdapter *nodeSelectionAdapter;
     QList<KisNodeDummy*> updateQueue;
     QTimer updateTimer;
 
     KisModelIndexConverterBase *indexConverter = 0;
     KisDummiesFacadeBase *dummiesFacade = 0;
-    bool needFinishRemoveRows = false;
-    bool needFinishInsertRows = false;
-    bool showRootLayer = false;
-    bool showGlobalSelection = false;
+    bool needFinishRemoveRows;
+    bool needFinishInsertRows;
+    bool showRootLayer;
+    bool showGlobalSelection;
+    QPersistentModelIndex activeNodeIndex;
 
     KisNodeDummy* parentOfRemovedNode = 0;
 };
@@ -235,7 +238,7 @@ void KisNodeModel::connectDummies(KisNodeDummy *dummy, bool needConnect)
     }
 }
 
-void KisNodeModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageWSP image, KisShapeController *shapeController)
+void KisNodeModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageWSP image, KisShapeController *shapeController, KisNodeSelectionAdapter *nodeSelectionAdapter)
 {
     KisDummiesFacadeBase *oldDummiesFacade;
     KisShapeController *oldShapeController;
@@ -243,6 +246,7 @@ void KisNodeModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImag
     oldDummiesFacade = m_d->dummiesFacade;
 
     m_d->shapeController = shapeController;
+    m_d->nodeSelectionAdapter = nodeSelectionAdapter;
 
     if(oldDummiesFacade && m_d->image) {
         m_d->image->disconnect(this);
@@ -426,6 +430,9 @@ QVariant KisNodeModel::data(const QModelIndex &index, int role) const
         KisNodeProgressProxy *proxy = node->nodeProgressProxy();
         return proxy ? proxy->percentage() : -1;
     }
+    case ActiveRole: {
+        return m_d->activeNodeIndex == index;
+    }
     default:
         if (role >= int(BeginThumbnailRole) && belongsToIsolatedGroup(node))
             return node->createThumbnail(role - int(BeginThumbnailRole), role - int(BeginThumbnailRole));
@@ -448,7 +455,6 @@ bool KisNodeModel::setData(const QModelIndex &index, const QVariant &value, int
 {
 
     if (role == ActiveRole || role == AlternateActiveRole) {
-
         QModelIndex parentIndex;
         if (!index.isValid() && m_d->parentOfRemovedNode && m_d->dummiesFacade && m_d->indexConverter) {
             parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode);
@@ -467,8 +473,18 @@ bool KisNodeModel::setData(const QModelIndex &index, const QVariant &value, int
             activatedNode = 0;
         }
 
+        QModelIndex newActiveNode = activatedNode ? indexFromNode(activatedNode) : QModelIndex();
+        if (role == ActiveRole && value.toBool() &&
+            m_d->activeNodeIndex == newActiveNode) {
 
-        emit nodeActivated(activatedNode);
+            return true;
+        }
+
+        m_d->activeNodeIndex = newActiveNode;
+
+        if (m_d->nodeSelectionAdapter) {
+            m_d->nodeSelectionAdapter->setActiveNode(activatedNode);
+        }
 
         if (role == AlternateActiveRole) {
             emit toggleIsolateActiveNode();
diff --git a/krita/ui/kis_node_model.h b/krita/ui/kis_node_model.h
index 9fd2270..ec32c3a 100644
--- a/krita/ui/kis_node_model.h
+++ b/krita/ui/kis_node_model.h
@@ -30,6 +30,7 @@ class KisDummiesFacadeBase;
 class KisNodeDummy;
 class KisShapeController;
 class KisModelIndexConverterBase;
+class KisNodeSelectionAdapter;
 
 /**
  * KisNodeModel offers a Qt model-view compatible view of the node
@@ -57,7 +58,7 @@ public: // from QAbstractItemModel
     KisNodeModel(QObject * parent);
     ~KisNodeModel();
 
-    void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageWSP image, KisShapeController *shapeController);
+    void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageWSP image, KisShapeController *shapeController, KisNodeSelectionAdapter *nodeSelectionAdapter);
     KisNodeSP nodeFromIndex(const QModelIndex &index) const;
     QModelIndex indexFromNode(KisNodeSP node) const;
 
diff --git a/krita/ui/kis_node_selection_adapter.cpp b/krita/ui/kis_node_selection_adapter.cpp
new file mode 100644
index 0000000..297790c
--- /dev/null
+++ b/krita/ui/kis_node_selection_adapter.cpp
@@ -0,0 +1,47 @@
+/*
+ *  Copyright (c) 2015 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  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_selection_adapter.h"
+
+#include "kis_node_manager.h"
+#include "kis_node.h"
+
+struct KisNodeSelectionAdapter::Private
+{
+    KisNodeManager *nodeManager;
+};
+
+KisNodeSelectionAdapter::KisNodeSelectionAdapter(KisNodeManager *nodeManager)
+    : m_d(new Private)
+{
+    m_d->nodeManager = nodeManager;
+}
+
+KisNodeSelectionAdapter::~KisNodeSelectionAdapter()
+{
+}
+
+KisNodeSP KisNodeSelectionAdapter::activeNode() const
+{
+    return m_d->nodeManager->activeNode();
+}
+
+void KisNodeSelectionAdapter::setActiveNode(KisNodeSP node)
+{
+    m_d->nodeManager->slotUiActivatedNode(node);
+}
diff --git a/krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.h b/krita/ui/kis_node_selection_adapter.h
similarity index 54%
copy from krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.h
copy to krita/ui/kis_node_selection_adapter.h
index b4ecb2f..2eb3e8a 100644
--- a/krita/plugins/extensions/dockers/defaultdockers/kis_node_juggler_compressed.h
+++ b/krita/ui/kis_node_selection_adapter.h
@@ -16,45 +16,28 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-#ifndef __KIS_NODE_JUGGLER_COMPRESSED_H
-#define __KIS_NODE_JUGGLER_COMPRESSED_H
+#ifndef __KIS_NODE_SELECTION_ADAPTER_H
+#define __KIS_NODE_SELECTION_ADAPTER_H
 
-#include <QObject>
 #include <QScopedPointer>
-
-#include <kritadefaultdockers_export.h>
-#include <kundo2command.h>
 #include "kis_types.h"
+#include "kritaui_export.h"
+
+class KisNodeManager;
 
 
-class KRITADEFAULTDOCKERS_EXPORT KisNodeJugglerCompressed : public QObject
+class KRITAUI_EXPORT KisNodeSelectionAdapter
 {
-    Q_OBJECT
 public:
-    KisNodeJugglerCompressed(const KUndo2MagicString &actionName, KisImageSP image, int timeout);
-    ~KisNodeJugglerCompressed();
-
-    void moveNode(KisNodeSP node, KisNodeSP parent, KisNodeSP above);
-    void setAutoDelete(bool value);
-
-    bool isEnded() const;
+    KisNodeSelectionAdapter(KisNodeManager *nodeManager);
+    ~KisNodeSelectionAdapter();
 
-    void lowerNode(KisNodeSP node);
-    void raiseNode(KisNodeSP node);
-
-public Q_SLOTS:
-    void end();
-
-private Q_SLOTS:
-    void slotUpdateTimeout();
-    void slotEndStrokeRequested();
-
-private:
-    void startTimers();
+    KisNodeSP activeNode() const;
+    void setActiveNode(KisNodeSP node);
 
 private:
     struct Private;
     const QScopedPointer<Private> m_d;
 };
 
-#endif /* __KIS_NODE_JUGGLER_COMPRESSED_H */
+#endif /* __KIS_NODE_SELECTION_ADAPTER_H */
diff --git a/krita/ui/tests/kis_model_index_converter_test.cpp b/krita/ui/tests/kis_model_index_converter_test.cpp
index 623fb13..4aecbc6 100644
--- a/krita/ui/tests/kis_model_index_converter_test.cpp
+++ b/krita/ui/tests/kis_model_index_converter_test.cpp
@@ -36,12 +36,12 @@ void KisModelIndexConverterTest::init()
     addSelectionMasks();
 
     m_dummiesFacade->setImage(m_image);
-    m_nodeModel->setDummiesFacade(m_dummiesFacade, m_image, 0);
+    m_nodeModel->setDummiesFacade(m_dummiesFacade, m_image, 0, 0);
 }
 
 void KisModelIndexConverterTest::cleanup()
 {
-    m_nodeModel->setDummiesFacade(0, 0, 0);
+    m_nodeModel->setDummiesFacade(0, 0, 0, 0);
     m_dummiesFacade->setImage(0);
 
     cleanupBase();
diff --git a/krita/ui/tests/kis_node_model_test.cpp b/krita/ui/tests/kis_node_model_test.cpp
index d808983..733fd80 100644
--- a/krita/ui/tests/kis_node_model_test.cpp
+++ b/krita/ui/tests/kis_node_model_test.cpp
@@ -56,14 +56,14 @@ void KisNodeModelTest::testSetImage()
 {
     constructImage();
     m_shapeController->setImage(m_image);
-    m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0);
+    m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0);
     new ModelTest(m_nodeModel, this);
 }
 
 void KisNodeModelTest::testAddNode()
 {
     m_shapeController->setImage(m_image);
-    m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0);
+    m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0);
     new ModelTest(m_nodeModel, this);
 
     constructImage();
@@ -74,7 +74,7 @@ void KisNodeModelTest::testRemoveAllNodes()
 {
     constructImage();
     m_shapeController->setImage(m_image);
-    m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0);
+    m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0);
     new ModelTest(m_nodeModel, this);
 
     m_image->removeNode(m_layer4);
@@ -87,7 +87,7 @@ void KisNodeModelTest::testRemoveIncludingRoot()
 {
     constructImage();
     m_shapeController->setImage(m_image);
-    m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0);
+    m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0);
     new ModelTest(m_nodeModel, this);
 
     m_image->removeNode(m_layer4);
@@ -103,7 +103,7 @@ void KisNodeModelTest::testSubstituteRootNode()
 {
     constructImage();
     m_shapeController->setImage(m_image);
-    m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0);
+    m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0);
     new ModelTest(m_nodeModel, this);
 
     m_image->flatten();


More information about the kimageshop mailing list