[krita] /: [4.1 only] Implement frames management action

Dmitry Kazakov null at kde.org
Tue Apr 10 15:55:59 UTC 2018


Git commit 25cea389c460c37e91867c812ebf39adbe19cb59 by Dmitry Kazakov.
Committed on 10/04/2018 at 15:53.
Pushed by dkazakov into branch 'master'.

[4.1 only] Implement frames management action

The full list of new actions:
1) Insert Keyframe Right
2) Insert Keyframe Left
3) Insert N Keyframes Right
4) Insert N Keyframes Left

5) Remove Frame and Shift
6) Remove Frame

7) Insert Column Right
8) Insert Column Left
9) Insert N Columns Right
10) Insert N Columns Left

11) Remove Column and Shift
12) Remove Column

I will probably change the naming of some actions in the future when
other planned actions like "Add/Remove hold frames" is implemented.


CC:kimageshop at kde.org

M  +158  -12   krita/krita.action
M  +7    -3    plugins/dockers/animation/kis_animation_utils.cpp
M  +1    -0    plugins/dockers/animation/kis_animation_utils.h
M  +22   -0    plugins/dockers/animation/kis_time_based_item_model.cpp
M  +2    -0    plugins/dockers/animation/kis_time_based_item_model.h
M  +0    -10   plugins/dockers/animation/timeline_docker.cpp
M  +52   -0    plugins/dockers/animation/timeline_frames_model.cpp
M  +2    -0    plugins/dockers/animation/timeline_frames_model.h
M  +278  -40   plugins/dockers/animation/timeline_frames_view.cpp
M  +25   -3    plugins/dockers/animation/timeline_frames_view.h
M  +39   -64   plugins/dockers/animation/timeline_ruler_header.cpp
M  +8    -5    plugins/dockers/animation/timeline_ruler_header.h

https://commits.kde.org/krita/25cea389c460c37e91867c812ebf39adbe19cb59

diff --git a/krita/krita.action b/krita/krita.action
index 0deae46fbdf..cdca2e1f2b5 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -2129,18 +2129,6 @@
       <isCheckable>true</isCheckable>
       <statusTip></statusTip>
     </Action>
-    <Action name="add_blank_frame">
-      <icon></icon>
-      <text>Add blank frame</text>
-      <whatsThis></whatsThis>
-      <toolTip>Add blank frame</toolTip>
-      <iconText>Add blank frame</iconText>
-      <activationFlags>100000</activationFlags>
-      <activationConditions>0</activationConditions>
-      <shortcut></shortcut>
-      <isCheckable>false</isCheckable>
-      <statusTip></statusTip>
-    </Action>
     <Action name="show_in_timeline">
       <icon></icon>
       <text>Show in Timeline</text>
@@ -2199,6 +2187,164 @@
       <isCheckable>false</isCheckable>
       <statusTip></statusTip>
     </Action>
+
+    <Action name="insert_keyframes_right">
+      <icon></icon>
+      <text>Insert Keyframe Right</text>
+      <whatsThis></whatsThis>
+      <toolTip>Insert keyframes to the right of selection moving the tail of animation to the right</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="insert_keyframes_left">
+      <icon></icon>
+      <text>Insert Keyframe Left</text>
+      <whatsThis></whatsThis>
+      <toolTip>Insert keyframes to the left of selection moving the tail of animation to the right</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="insert_n_keyframes_right">
+      <icon></icon>
+      <text>Insert N Keyframes Right</text>
+      <whatsThis></whatsThis>
+      <toolTip>Insert several keyframes to the right of selection moving the tail of animation to the right</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="insert_n_keyframes_left">
+      <icon></icon>
+      <text>Insert N Keyframes Left</text>
+      <whatsThis></whatsThis>
+      <toolTip>Insert several keyframes to the left of selection moving the tail of animation to the right</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="remove_frames_and_shift">
+      <icon></icon>
+      <text>Remove Frame and Shift</text>
+      <whatsThis></whatsThis>
+      <toolTip>Remove keyframes moving the tail of animation to the left</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="remove_frames">
+      <icon></icon>
+      <text>Remove Frame</text>
+      <whatsThis></whatsThis>
+      <toolTip>Just remove keyframes without moving anything around</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="insert_columns_right">
+      <icon></icon>
+      <text>Insert Column Right</text>
+      <whatsThis></whatsThis>
+      <toolTip>Insert keyframes to the right of selection moving the tail of animation to the right</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="insert_columns_left">
+      <icon></icon>
+      <text>Insert Column Left</text>
+      <whatsThis></whatsThis>
+      <toolTip>Insert column to the left of selection moving the tail of animation to the right</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="insert_n_columns_right">
+      <icon></icon>
+      <text>Insert N Columns Right</text>
+      <whatsThis></whatsThis>
+      <toolTip>Insert several columns to the right of selection moving the tail of animation to the right</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="insert_n_columns_left">
+      <icon></icon>
+      <text>Insert N Columns Left</text>
+      <whatsThis></whatsThis>
+      <toolTip>Insert several columns to the left of selection moving the tail of animation to the right</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="remove_columns_and_shift">
+      <icon></icon>
+      <text>Remove Column and Shift</text>
+      <whatsThis></whatsThis>
+      <toolTip>Remove columns moving the tail of animation to the left</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="remove_columns">
+      <icon></icon>
+      <text>Remove Column</text>
+      <whatsThis></whatsThis>
+      <toolTip>Just remove columns without moving anything around</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+
   </Actions>
 
 
diff --git a/plugins/dockers/animation/kis_animation_utils.cpp b/plugins/dockers/animation/kis_animation_utils.cpp
index 82979e9b83c..85f61620a3a 100644
--- a/plugins/dockers/animation/kis_animation_utils.cpp
+++ b/plugins/dockers/animation/kis_animation_utils.cpp
@@ -50,12 +50,11 @@ namespace KisAnimationUtils {
     const QString removeOpacityKeyframeActionName = i18n("Remove opacity keyframe");
     const QString removeTransformKeyframeActionName = i18n("Remove transform keyframe");
 
-    void createKeyframeLazy(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy) {
-        KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked());
-
+    KUndo2Command* createKeyframeCommand(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy, KUndo2Command *parentCommand) {
         KUndo2Command *cmd = new KisCommandUtils::LambdaCommand(
             copy ? kundo2_i18n("Copy Keyframe") :
                    kundo2_i18n("Add Keyframe"),
+            parentCommand,
 
             [image, node, channelId, time, copy] () mutable -> KUndo2Command* {
                 bool result = false;
@@ -101,6 +100,11 @@ namespace KisAnimationUtils {
                 return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : nullptr;
         });
 
+        return cmd;
+    }
+
+    void createKeyframeLazy(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy) {
+        KUndo2Command *cmd = createKeyframeCommand(image, node, channelId, time, copy);
         KisProcessingApplicator::runSingleCommandStroke(image, cmd, KisStrokeJobData::BARRIER);
     }
 
diff --git a/plugins/dockers/animation/kis_animation_utils.h b/plugins/dockers/animation/kis_animation_utils.h
index 902ffca80f2..9b3cd4712f7 100644
--- a/plugins/dockers/animation/kis_animation_utils.h
+++ b/plugins/dockers/animation/kis_animation_utils.h
@@ -25,6 +25,7 @@
 
 namespace KisAnimationUtils
 {
+    KUndo2Command* createKeyframeCommand(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy, KUndo2Command *parentCommand = 0);
     void createKeyframeLazy(KisImageSP image, KisNodeSP node, const QString &channel, int time, bool copy);
 
     struct FrameItem {
diff --git a/plugins/dockers/animation/kis_time_based_item_model.cpp b/plugins/dockers/animation/kis_time_based_item_model.cpp
index 46705bf75c5..ceb44a88673 100644
--- a/plugins/dockers/animation/kis_time_based_item_model.cpp
+++ b/plugins/dockers/animation/kis_time_based_item_model.cpp
@@ -335,6 +335,28 @@ bool KisTimeBasedItemModel::offsetFrames(QModelIndexList srcIndexes, const QPoin
     return cmd;
 }
 
+bool KisTimeBasedItemModel::removeFramesAndOffset(const QModelIndexList &indexes)
+{
+    if (indexes.isEmpty()) return true;
+
+    KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Remove frame and shift", "Remove %1 frames and shift", indexes.size()));
+
+    {
+        KisImageBarrierLockerWithFeedback locker(m_d->image);
+
+        Q_FOREACH (const QModelIndex &index, indexes) {
+            QModelIndexList movedIndexes;
+            for (int column = index.column() + 1; column < columnCount(); column++) {
+                movedIndexes << this->index(index.row(), column);
+            }
+            createOffsetFramesCommand(movedIndexes, QPoint(-1, 0), false, parentCommand);
+        }
+    }
+
+    KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, KisStrokeJobData::BARRIER);
+    return true;
+}
+
 void KisTimeBasedItemModel::slotInternalScrubPreviewRequested(int time)
 {
     if (m_d->animationPlayer && !m_d->animationPlayer->isPlaying()) {
diff --git a/plugins/dockers/animation/kis_time_based_item_model.h b/plugins/dockers/animation/kis_time_based_item_model.h
index 08428c54fae..25626e82e31 100644
--- a/plugins/dockers/animation/kis_time_based_item_model.h
+++ b/plugins/dockers/animation/kis_time_based_item_model.h
@@ -54,6 +54,8 @@ public:
     bool removeFrames(const QModelIndexList &indexes);
     bool offsetFrames(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames);
 
+    bool removeFramesAndOffset(const QModelIndexList &indexes);
+
     void setScrubState(bool active);
     void scrubTo(int time, bool preview);
 
diff --git a/plugins/dockers/animation/timeline_docker.cpp b/plugins/dockers/animation/timeline_docker.cpp
index acff860c8ae..b166ff7db6f 100644
--- a/plugins/dockers/animation/timeline_docker.cpp
+++ b/plugins/dockers/animation/timeline_docker.cpp
@@ -144,16 +144,6 @@ void TimelineDocker::setMainWindow(KisViewManager *view)
 {
     KisActionManager *actionManager = view->actionManager();
 
-    QMap<QString, KisAction*> actions = m_d->view->globalActions();
-
-    QMap<QString, KisAction*>::const_iterator it = actions.constBegin();
-    QMap<QString, KisAction*>::const_iterator end = actions.constEnd();
-
-    for (; it != end; ++it) {
-        actionManager->addAction(it.key(), it.value());
-    }
-
     m_d->view->setShowInTimeline(actionManager->actionByName("show_in_timeline"));
-
     m_d->view->setActionManager(actionManager);
 }
diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp
index 65ec9653dc8..6e28cc872ed 100644
--- a/plugins/dockers/animation/timeline_frames_model.cpp
+++ b/plugins/dockers/animation/timeline_frames_model.cpp
@@ -40,6 +40,7 @@
 #include "kundo2command.h"
 #include "kis_post_execution_undo_adapter.h"
 #include <commands/kis_node_property_list_command.h>
+#include <commands_new/kis_switch_current_time_command.h>
 
 #include "kis_animation_utils.h"
 #include "timeline_color_scheme.h"
@@ -50,6 +51,9 @@
 #include "kis_node_view_color_scheme.h"
 #include "krita_utils.h"
 #include <QApplication>
+#include "kis_processing_applicator.h"
+#include <KisImageBarrierLockerWithFeedback.h>
+
 
 struct TimelineFramesModel::Private
 {
@@ -688,6 +692,54 @@ bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex)
     return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true);
 }
 
+bool TimelineFramesModel::insertFrames(int dstColumn, const QList<int> &dstRows, int count)
+{
+    if (dstRows.isEmpty() || count <= 0) return true;
+
+    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 - 1);
+
+
+        createOffsetFramesCommand(indexes, QPoint(count, 0), 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; column++) {
+                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 - 1;
+
+        new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(),
+                                        oldTime,
+                                        newTime, parentCommand);
+    }
+
+    KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, KisStrokeJobData::BARRIER);
+
+    return true;
+}
+
+
 QString TimelineFramesModel::audioChannelFileName() const
 {
     return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString();
diff --git a/plugins/dockers/animation/timeline_frames_model.h b/plugins/dockers/animation/timeline_frames_model.h
index af086b11671..2c7908438fe 100644
--- a/plugins/dockers/animation/timeline_frames_model.h
+++ b/plugins/dockers/animation/timeline_frames_model.h
@@ -51,6 +51,8 @@ public:
     bool createFrame(const QModelIndex &dstIndex);
     bool copyFrame(const QModelIndex &dstIndex);
 
+    bool insertFrames(int dstColumn, const QList<int> &dstRows, int count);
+
     QString audioChannelFileName() const;
     void setAudioChannelFileName(const QString &fileName);
 
diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp
index d42f1c582d7..d9a769bf9a6 100644
--- a/plugins/dockers/animation/timeline_frames_view.cpp
+++ b/plugins/dockers/animation/timeline_frames_view.cpp
@@ -61,6 +61,7 @@
 #include <KoIconToolTip.h>
 #include <QStandardPaths>
 #include <QWidgetAction>
+#include <QInputDialog>
 
 #include "config-qtmultimedia.h"
 
@@ -110,12 +111,6 @@ struct TimelineFramesView::Private
     QMenu *layerEditingMenu;
     QMenu *existingLayersMenu;
 
-    QMenu *frameCreationMenu;
-    QMenu *frameEditingMenu;
-
-    QMenu *multipleFrameEditingMenu;
-    QMap<QString, KisAction*> globalActions;
-
     KisZoomButton *zoomDragButton;
 
     bool dragInProgress;
@@ -158,6 +153,15 @@ TimelineFramesView::TimelineFramesView(QWidget *parent)
     m_d->horizontalRuler = new TimelineRulerHeader(this);
     this->setHorizontalHeader(m_d->horizontalRuler);
 
+    connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsLeft()), SLOT(slotInsertColumnsLeft()));
+    connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsRight()), SLOT(slotInsertColumnsRight()));
+
+    connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsLeftCustom()), SLOT(slotInsertColumnsLeftCustom()));
+    connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsRightCustom()), SLOT(slotInsertColumnsRightCustom()));
+
+    connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumns()), SLOT(slotRemoveColumns()));
+    connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumnsAndShift()), SLOT(slotRemoveColumnsAndShift()));
+
     m_d->layersHeader = new TimelineLayersHeader(this);
 
     m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed);
@@ -239,28 +243,16 @@ TimelineFramesView::TimelineFramesView(QWidget *parent)
 
     /********** Frame Editing Context Menu ***********************************************/
 
-    m_d->frameCreationMenu = new QMenu(this);
-    m_d->frameCreationMenu->addAction(KisAnimationUtils::addFrameActionName, this, SLOT(slotNewFrame()));
-    m_d->frameCreationMenu->addAction(KisAnimationUtils::duplicateFrameActionName, this, SLOT(slotCopyFrame()));
-
     m_d->colorSelector = new KisColorLabelSelectorWidget(this);
     m_d->colorSelectorAction = new QWidgetAction(this);
     m_d->colorSelectorAction->setDefaultWidget(m_d->colorSelector);
     connect(m_d->colorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged);
 
-    m_d->frameEditingMenu = new QMenu(this);
-    m_d->frameEditingMenu->addAction(KisAnimationUtils::removeFrameActionName, this, SLOT(slotRemoveFrame()));
-    m_d->frameEditingMenu->addAction(m_d->colorSelectorAction);
-
     m_d->multiframeColorSelector = new KisColorLabelSelectorWidget(this);
     m_d->multiframeColorSelectorAction = new QWidgetAction(this);
     m_d->multiframeColorSelectorAction->setDefaultWidget(m_d->multiframeColorSelector);
     connect(m_d->multiframeColorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged);
 
-    m_d->multipleFrameEditingMenu = new QMenu(this);
-    m_d->multipleFrameEditingMenu->addAction(KisAnimationUtils::removeFramesActionName, this, SLOT(slotRemoveFrame()));
-    m_d->multipleFrameEditingMenu->addAction(m_d->multiframeColorSelectorAction);
-
     /********** Zoom Button **************************************************************/
 
     m_d->zoomDragButton = new KisZoomButton(this);
@@ -283,28 +275,61 @@ TimelineFramesView::~TimelineFramesView()
 {
 }
 
-QMap<QString, KisAction*> TimelineFramesView::globalActions() const
-{
-    return m_d->globalActions;
-}
-
 void TimelineFramesView::setShowInTimeline(KisAction* action)
 {
     m_d->showHideLayerAction = action;
     m_d->layerEditingMenu->addAction(m_d->showHideLayerAction);
 }
 
-
 void TimelineFramesView::setActionManager( KisActionManager * actionManager)
 {
     m_d->actionMan = actionManager;
     m_d->horizontalRuler->setActionManager(actionManager);
+
+
+    if (actionManager) {
+        KisAction *action = 0;
+
+        // TODO: move here!
+        // "add_blank_frame" is initialized in AnimationDocker
+        // "add_duplicate_frame" is initialized in AnimationDocker
+
+        action = m_d->actionMan->createAction("insert_keyframes_right");
+        connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesRight()));
+
+        action = m_d->actionMan->createAction("insert_n_keyframes_right");
+        connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesRightCustom()));
+
+        action = m_d->actionMan->createAction("insert_keyframes_left");
+        connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesLeft()));
+
+        action = m_d->actionMan->createAction("insert_n_keyframes_left");
+        connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesLeftCustom()));
+
+        action = m_d->actionMan->createAction("remove_frames_and_shift");
+        connect(action, SIGNAL(triggered()), SLOT(slotRemoveFramesAndShift()));
+
+        action = m_d->actionMan->createAction("remove_frames");
+        connect(action, SIGNAL(triggered()), SLOT(slotRemoveFrame()));
+    }
 }
 
+void TimelineFramesView::createFrameEditingMenu()
+{
 
+}
+
+KisAction *TimelineFramesView::addActionToMenu(QMenu *menu, const QString &actionId)
+{
+    KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->actionMan, 0);
 
+    KisAction *action = m_d->actionMan->actionByName(actionId);
+    KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(action, 0);
 
+    menu->addAction(action);
 
+    return action;
+}
 
 void resizeToMinimalSize(QAbstractButton *w, int minimalSize) {
     QSize buttonSize = w->sizeHint();
@@ -626,7 +651,7 @@ void TimelineFramesView::slotDataChanged(const QModelIndex &topLeft, const QMode
 
     if (selectedColumn != index.column() && !m_d->dragInProgress) {
         int row= index.isValid() ? index.row() : 0;
-        setCurrentIndex(m_d->model->index(row, selectedColumn));
+        selectionModel()->setCurrentIndex(m_d->model->index(row, selectedColumn), QItemSelectionModel::ClearAndSelect);
     }
 }
 
@@ -901,9 +926,41 @@ void TimelineFramesView::mousePressEvent(QMouseEvent *event)
                     m_d->colorSelector->setCurrentIndex(labelIndex);
                 }
 
-                m_d->frameEditingMenu->exec(event->globalPos());
+                QMenu menu;
+                addActionToMenu(&menu, "insert_keyframes_right");
+                addActionToMenu(&menu, "insert_keyframes_left");
+                menu.addSeparator();
+                addActionToMenu(&menu, "insert_n_keyframes_right");
+                addActionToMenu(&menu, "insert_n_keyframes_left");
+                menu.addSeparator();
+                addActionToMenu(&menu, "remove_frames");
+                addActionToMenu(&menu, "remove_frames_and_shift");
+                menu.addSeparator();
+                menu.addAction(m_d->colorSelectorAction);
+                menu.exec(event->globalPos());
+
             } else {
-                m_d->frameCreationMenu->exec(event->globalPos());
+                {
+                    KisSignalsBlocker b(m_d->colorSelector);
+                    KisImageConfig cfg;
+                    const int labelIndex = cfg.defaultFrameColorLabel();
+                    m_d->colorSelector->setCurrentIndex(labelIndex);
+                }
+
+                QMenu menu;
+                addActionToMenu(&menu, "add_blank_frame");
+                addActionToMenu(&menu, "add_duplicate_frame");
+                menu.addSeparator();
+                addActionToMenu(&menu, "insert_keyframes_right");
+                addActionToMenu(&menu, "insert_keyframes_left");
+                menu.addSeparator();
+                addActionToMenu(&menu, "insert_n_keyframes_right");
+                addActionToMenu(&menu, "insert_n_keyframes_left");
+                menu.addSeparator();
+                addActionToMenu(&menu, "remove_frames_and_shift");
+                menu.addSeparator();
+                menu.addAction(m_d->colorSelectorAction);
+                menu.exec(event->globalPos());
             }
         } else if (numSelectedItems > 1) {
             int labelIndex = -1;
@@ -923,15 +980,27 @@ void TimelineFramesView::mousePressEvent(QMouseEvent *event)
                 }
             }
 
-            if (!haveFrames) {
-                m_d->multiframeColorSelectorAction->setVisible(false);
-            } else {
+            if (haveFrames) {
                 KisSignalsBlocker b(m_d->multiframeColorSelector);
                 m_d->multiframeColorSelector->setCurrentIndex(labelIndex);
-                m_d->multiframeColorSelectorAction->setVisible(true);
             }
 
-            m_d->multipleFrameEditingMenu->exec(event->globalPos());
+            QMenu menu;
+            addActionToMenu(&menu, "insert_keyframes_right");
+            addActionToMenu(&menu, "insert_keyframes_left");
+            menu.addSeparator();
+            addActionToMenu(&menu, "insert_n_keyframes_right");
+            addActionToMenu(&menu, "insert_n_keyframes_left");
+            menu.addSeparator();
+            if (haveFrames) {
+                addActionToMenu(&menu, "remove_frames");
+            }
+            addActionToMenu(&menu, "remove_frames_and_shift");
+            if (haveFrames) {
+                menu.addSeparator();
+                menu.addAction(m_d->multiframeColorSelectorAction);
+            }
+            menu.exec(event->globalPos());
         }
     } else if (event->button() == Qt::MidButton) {
         QModelIndex index = model()->buddy(indexAt(event->pos()));
@@ -1061,6 +1130,8 @@ void TimelineFramesView::slotRemoveLayer()
     model()->removeRow(index.row());
 }
 
+
+// TODO: this method is unused atm, please forward the actions from Animation Docker here!
 void TimelineFramesView::slotNewFrame()
 {
     QModelIndex index = currentIndex();
@@ -1073,6 +1144,7 @@ void TimelineFramesView::slotNewFrame()
     m_d->model->createFrame(index);
 }
 
+// TODO: this method is unused atm, please forward the actions from Animation Docker here!
 void TimelineFramesView::slotCopyFrame()
 {
     QModelIndex index = currentIndex();
@@ -1085,23 +1157,189 @@ void TimelineFramesView::slotCopyFrame()
     m_d->model->copyFrame(index);
 }
 
-void TimelineFramesView::slotRemoveFrame()
+void TimelineFramesView::calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet<int> &rows)
 {
-    QModelIndexList indexes = selectionModel()->selectedIndexes();
+    minColumn = std::numeric_limits<int>::max();
+    maxColumn = std::numeric_limits<int>::min();
 
-    for (auto it = indexes.begin(); it != indexes.end(); /*noop*/) {
-        if (!m_d->model->data(*it, TimelineFramesModel::FrameEditableRole).toBool()) {
-            it = indexes.erase(it);
-        } else {
-            ++it;
+    Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) {
+        if (!m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) continue;
+
+        rows.insert(index.row());
+        minColumn = qMin(minColumn, index.column());
+        maxColumn = qMax(maxColumn, index.column());
+    }
+}
+
+void TimelineFramesView::insertFramesImpl(int insertAtColumn, int count, QSet<int> rows, bool forceEntireColumn)
+{
+    if (forceEntireColumn) {
+        rows.clear();
+        for (int i = 0; i < m_d->model->rowCount(); i++) {
+            if (!m_d->model->data(m_d->model->index(i, insertAtColumn), TimelineFramesModel::FrameEditableRole).toBool()) continue;
+            rows.insert(i);
+        }
+    }
+
+    if (!rows.isEmpty()) {
+        m_d->model->insertFrames(insertAtColumn, rows.toList(), count);
+    }
+}
+
+void TimelineFramesView::slotInsertKeyframesLeft(int count, bool forceEntireColumn)
+{
+    QSet<int> rows;
+    int minColumn = 0;
+    int maxColumn = 0;
+
+    calculateSelectionMetrics(minColumn, maxColumn, rows);
+
+    if (count <= 0) {
+        count = qMax(1, maxColumn - minColumn + 1);
+    }
+
+    insertFramesImpl(minColumn, count, rows, forceEntireColumn);
+}
+
+void TimelineFramesView::slotInsertKeyframesRight(int count, bool forceEntireColumn)
+{
+    QSet<int> rows;
+    int minColumn = 0;
+    int maxColumn = 0;
+
+    calculateSelectionMetrics(minColumn, maxColumn, rows);
+
+    if (count <= 0) {
+        count = qMax(1, maxColumn - minColumn + 1);
+    }
+
+    insertFramesImpl(maxColumn + 1, count, rows, forceEntireColumn);
+}
+
+void TimelineFramesView::slotInsertColumnsLeft(int count)
+{
+    slotInsertKeyframesLeft(count, true);
+}
+
+void TimelineFramesView::slotInsertColumnsRight(int count)
+{
+    slotInsertKeyframesRight(count, true);
+}
+
+void TimelineFramesView::slotInsertKeyframesLeftCustom()
+{
+    bool ok = false;
+
+    // TODO: save previous entered value till the end of the session
+    const int count = QInputDialog::getInt(this,
+                                           i18nc("@title:window", "Insert left"),
+                                           i18nc("@label:spinbox", "Enter number of frames"),
+                                           1, 1, 10000, 1, &ok);
+
+    if (ok) {
+        slotInsertKeyframesLeft(count);
+    }
+}
+
+
+void TimelineFramesView::slotInsertKeyframesRightCustom()
+{
+    bool ok = false;
+
+    // TODO: save previous entered value till the end of the session
+    const int count = QInputDialog::getInt(this,
+                                           i18nc("@title:window", "Insert right"),
+                                           i18nc("@label:spinbox", "Enter number of frames"),
+                                           1, 1, 10000, 1, &ok);
+
+    if (ok) {
+        slotInsertKeyframesRight(count);
+    }
+}
+
+void TimelineFramesView::slotInsertColumnsLeftCustom()
+{
+    bool ok = false;
+
+    // TODO: save previous entered value till the end of the session
+    const int count = QInputDialog::getInt(this,
+                                           i18nc("@title:window", "Insert left"),
+                                           i18nc("@label:spinbox", "Enter number of columns"),
+                                           1, 1, 10000, 1, &ok);
+
+    if (ok) {
+        slotInsertColumnsLeft(count);
+    }
+}
+
+
+void TimelineFramesView::slotInsertColumnsRightCustom()
+{
+    bool ok = false;
+
+    // TODO: save previous entered value till the end of the session
+    const int count = QInputDialog::getInt(this,
+                                           i18nc("@title:window", "Insert right"),
+                                           i18nc("@label:spinbox", "Enter number of columns"),
+                                           1, 1, 10000, 1, &ok);
+
+    if (ok) {
+        slotInsertColumnsRight(count);
+    }
+}
+
+void TimelineFramesView::slotRemoveFrame(bool forceEntireColumn, bool needsOffset)
+{
+    QModelIndexList indexes;
+
+    if (forceEntireColumn) {
+        QSet<int> rows;
+        int minColumn = 0;
+        int maxColumn = 0;
+
+        calculateSelectionMetrics(minColumn, maxColumn, rows);
+
+        rows.clear();
+        for (int i = 0; i < m_d->model->rowCount(); i++) {
+            if (!m_d->model->data(m_d->model->index(i, minColumn), TimelineFramesModel::FrameEditableRole).toBool()) continue;
+
+            for (int column = minColumn; column <= maxColumn; column++) {
+                indexes << m_d->model->index(i, column);
+            }
+        }
+    } else {
+        Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) {
+            if (m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) {
+                indexes << index;
+            }
         }
     }
 
     if (!indexes.isEmpty()) {
-        m_d->model->removeFrames(indexes);
+        if (needsOffset) {
+            m_d->model->removeFramesAndOffset(indexes);
+        } else {
+            m_d->model->removeFrames(indexes);
+        }
     }
 }
 
+void TimelineFramesView::slotRemoveColumns()
+{
+    slotRemoveFrame(true);
+}
+
+void TimelineFramesView::slotRemoveFramesAndShift(bool forceEntireColumn)
+{
+    slotRemoveFrame(forceEntireColumn, true);
+}
+
+void TimelineFramesView::slotRemoveColumnsAndShift()
+{
+    slotRemoveFramesAndShift(true);
+}
+
+
 bool TimelineFramesView::viewportEvent(QEvent *event)
 {
     if (event->type() == QEvent::ToolTip && model()) {
diff --git a/plugins/dockers/animation/timeline_frames_view.h b/plugins/dockers/animation/timeline_frames_view.h
index 4742154ff5f..1290068e81e 100644
--- a/plugins/dockers/animation/timeline_frames_view.h
+++ b/plugins/dockers/animation/timeline_frames_view.h
@@ -39,8 +39,6 @@ public:
 
     void updateGeometries() override;
 
-    QMap<QString, KisAction*> globalActions() const;
-
     void setShowInTimeline(KisAction *action);
 
     void setActionManager( KisActionManager * actionManager);
@@ -62,7 +60,25 @@ private Q_SLOTS:
 
     void slotNewFrame();
     void slotCopyFrame();
-    void slotRemoveFrame();
+
+
+    void slotInsertKeyframesLeft(int count = -1, bool forceEntireColumn = false);
+    void slotInsertKeyframesRight(int count = -1, bool forceEntireColumn = false);
+
+    void slotInsertKeyframesLeftCustom();
+    void slotInsertKeyframesRightCustom();
+
+    void slotRemoveFrame(bool forceEntireColumn = false, bool needsOffset = false);
+    void slotRemoveFramesAndShift(bool forceEntireColumn = false);
+
+    void slotInsertColumnsLeft(int count = -1);
+    void slotInsertColumnsLeftCustom();
+
+    void slotInsertColumnsRight(int count = -1);
+    void slotInsertColumnsRightCustom();
+
+    void slotRemoveColumns();
+    void slotRemoveColumnsAndShift();
 
     void slotReselectCurrentIndex();
 
@@ -85,6 +101,12 @@ private Q_SLOTS:
 
 private:
     void setFramesPerSecond(int fps);
+    void calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet<int> &rows);
+
+    void createFrameEditingMenu();
+
+    KisAction* addActionToMenu(QMenu *menu, const QString &actionId);
+    void insertFramesImpl(int insertAtColumn, int count, QSet<int> rows, bool forceEntireColumn);
 
 protected:
     QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index,
diff --git a/plugins/dockers/animation/timeline_ruler_header.cpp b/plugins/dockers/animation/timeline_ruler_header.cpp
index 95588f29ed6..67df7dedc63 100644
--- a/plugins/dockers/animation/timeline_ruler_header.cpp
+++ b/plugins/dockers/animation/timeline_ruler_header.cpp
@@ -39,12 +39,6 @@ struct TimelineRulerHeader::Private
 
     int fps;
 
-    QMenu *columnEditingMenu;
-    KisAction *insertLeftAction;
-    KisAction *insertRightAction;
-    KisAction *removeAction;
-    KisAction *clearAction;
-
     KisTimeBasedItemModel *model;
     int lastPressSectionIndex;
 
@@ -76,24 +70,27 @@ void TimelineRulerHeader::setActionManager( KisActionManager * actionManager)
 {
     m_d->actionMan = actionManager;
 
-    m_d->insertLeftAction = actionManager->createAction("insert_n_frames_left");
-    connect(m_d->insertLeftAction, SIGNAL(triggered()), SLOT(slotInsertColumnLeft()));
+    if (actionManager) {
+        KisAction *action;
+
+        action = actionManager->createAction("insert_columns_right");
+        connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnsRight()));
 
-    m_d->insertRightAction = actionManager->createAction("insert_n_frames_right");
-    connect(m_d->insertRightAction, SIGNAL(triggered()), SLOT(slotInsertColumnRight()));
+        action = actionManager->createAction("insert_n_columns_right");
+        connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnsRightCustom()));
 
-    m_d->clearAction = actionManager->createAction("clear_animation_columns");
-    connect(m_d->clearAction, SIGNAL(triggered()), SLOT(slotClearColumns()));
+        action = actionManager->createAction("insert_columns_left");
+        connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnsLeft()));
 
-    m_d->removeAction = actionManager->createAction("remove_animation_columns");
-    connect(m_d->removeAction, SIGNAL(triggered()), SLOT(slotRemoveColumns()));
+        action = actionManager->createAction("insert_n_columns_left");
+        connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnsLeftCustom()));
 
+        action = actionManager->createAction("remove_columns_and_shift");
+        connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveColumnsAndShift()));
 
-    m_d->columnEditingMenu = new QMenu(this);
-    m_d->columnEditingMenu->addAction(static_cast<QAction*>(m_d->insertLeftAction));
-    m_d->columnEditingMenu->addAction(static_cast<QAction*>(m_d->insertRightAction));
-    m_d->columnEditingMenu->addAction(static_cast<QAction*>(m_d->clearAction));
-    m_d->columnEditingMenu->addAction(static_cast<QAction*>(m_d->removeAction));
+        action = actionManager->createAction("remove_columns");
+        connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveColumns()));
+    }
 }
 
 
@@ -403,6 +400,19 @@ int getColumnCount(const QModelIndexList &indexes, int *leftmostCol, int *rightm
     return columns.size();
 }
 
+KisAction *TimelineRulerHeader::addActionToMenu(QMenu *menu, const QString &actionId)
+{
+    KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->actionMan, 0);
+
+    KisAction *action = m_d->actionMan->actionByName(actionId);
+    KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(action, 0);
+
+    menu->addAction(action);
+
+    return action;
+}
+
+
 void TimelineRulerHeader::mousePressEvent(QMouseEvent *e)
 {
     int logical = logicalIndexAt(e->pos());
@@ -415,13 +425,18 @@ void TimelineRulerHeader::mousePressEvent(QMouseEvent *e)
                 model()->setHeaderData(logical, orientation(), true, KisTimeBasedItemModel::ActiveFrameRole);
             }
 
+            QMenu menu;
+            addActionToMenu(&menu, "insert_columns_right");
+            addActionToMenu(&menu, "insert_columns_left");
+            menu.addSeparator();
+            addActionToMenu(&menu, "insert_n_columns_right");
+            addActionToMenu(&menu, "insert_n_columns_left");
+            menu.addSeparator();
+            addActionToMenu(&menu, "remove_columns");
+            addActionToMenu(&menu, "remove_columns_and_shift");
 
-            m_d->insertLeftAction->setText(i18n("Insert %1 left", numSelectedColumns));
-            m_d->insertRightAction->setText(i18n("Insert %1 right", numSelectedColumns));
-            m_d->clearAction->setText(i18n("Clear %1 columns", numSelectedColumns));
-            m_d->removeAction->setText(i18n("Remove %1 columns", numSelectedColumns));
+            menu.exec(e->globalPos());
 
-            m_d->columnEditingMenu->exec(e->globalPos());
             return;
 
         } else if (e->button() == Qt::LeftButton) {
@@ -486,43 +501,3 @@ QModelIndexList TimelineRulerHeader::Private::prepareFramesSlab(int startCol, in
 
     return frames;
 }
-
-void TimelineRulerHeader::slotInsertColumnLeft()
-{
-    QModelIndexList selectedIndexes = selectionModel()->selectedIndexes();
-    int leftmostCol = 0, rightmostCol = 0;
-    int numColumns = getColumnCount(selectedIndexes, &leftmostCol, &rightmostCol);
-
-    QModelIndexList movingFrames = m_d->prepareFramesSlab(leftmostCol, m_d->model->columnCount() - 1);
-    m_d->model->offsetFrames(movingFrames, QPoint(numColumns, 0), false);
-}
-
-void TimelineRulerHeader::slotInsertColumnRight()
-{
-    QModelIndexList selectedIndexes = selectionModel()->selectedIndexes();
-    int leftmostCol = 0, rightmostCol = 0;
-    int numColumns = getColumnCount(selectedIndexes, &leftmostCol, &rightmostCol);
-
-    QModelIndexList movingFrames = m_d->prepareFramesSlab(rightmostCol + 1, m_d->model->columnCount() - 1);
-    m_d->model->offsetFrames(movingFrames, QPoint(numColumns, 0), false);
-}
-
-void TimelineRulerHeader::slotClearColumns(bool removeColumns)
-{
-    QModelIndexList selectedIndexes = selectionModel()->selectedIndexes();
-    int leftmostCol = 0, rightmostCol = 0;
-    int numColumns = getColumnCount(selectedIndexes, &leftmostCol, &rightmostCol);
-
-    QModelIndexList movingFrames = m_d->prepareFramesSlab(leftmostCol, rightmostCol);
-    m_d->model->removeFrames(movingFrames);
-
-    if (removeColumns) {
-        QModelIndexList movingFrames = m_d->prepareFramesSlab(rightmostCol + 1, m_d->model->columnCount() - 1);
-        m_d->model->offsetFrames(movingFrames, QPoint(-numColumns, 0), false);
-    }
-}
-
-void TimelineRulerHeader::slotRemoveColumns()
-{
-    slotClearColumns(true);
-}
diff --git a/plugins/dockers/animation/timeline_ruler_header.h b/plugins/dockers/animation/timeline_ruler_header.h
index f0bd04b72fa..818015564db 100644
--- a/plugins/dockers/animation/timeline_ruler_header.h
+++ b/plugins/dockers/animation/timeline_ruler_header.h
@@ -50,6 +50,7 @@ protected:
     void changeEvent(QEvent *event) override;
 
 private:
+    KisAction *addActionToMenu(QMenu *menu, const QString &actionId);
     void updateMinimumSize();
 
     void paintSpan(QPainter *painter, int userFrameId,
@@ -60,11 +61,13 @@ private:
                    const QPalette &palette,
                    const QPen &gridPen) const;
 
-private Q_SLOTS:
-    void slotInsertColumnLeft();
-    void slotInsertColumnRight();
-    void slotClearColumns(bool removeColumns = false);
-    void slotRemoveColumns();
+Q_SIGNALS:
+    void sigInsertColumnsLeft();
+    void sigInsertColumnsRight();
+    void sigInsertColumnsLeftCustom();
+    void sigInsertColumnsRightCustom();
+    void sigRemoveColumns();
+    void sigRemoveColumnsAndShift();
 
 private:
     struct Private;


More information about the kimageshop mailing list