[krita] /: Implement Mirror Frames feature

Dmitry Kazakov null at kde.org
Thu Apr 12 18:50:08 UTC 2018


Git commit c88391e465e62b43c6a23d0aca9e72ca90e01d8a by Dmitry Kazakov.
Committed on 12/04/2018 at 18:47.
Pushed by dkazakov into branch 'master'.

Implement Mirror Frames feature

One can select some frames range, it will be mirrored. It is also
possible to mirror columns.

CC:kimageshop at kde.org

M  +26   -1    krita/krita.action
M  +59   -0    libs/image/kis_keyframe_channel.cpp
M  +3    -0    libs/image/kis_keyframe_channel.h
M  +18   -0    libs/image/kis_keyframe_commands.cpp
M  +14   -0    libs/image/kis_keyframe_commands.h
M  +50   -0    plugins/dockers/animation/kis_time_based_item_model.cpp
M  +2    -0    plugins/dockers/animation/kis_time_based_item_model.h
M  +31   -2    plugins/dockers/animation/timeline_frames_view.cpp
M  +6    -1    plugins/dockers/animation/timeline_frames_view.h
M  +8    -0    plugins/dockers/animation/timeline_ruler_header.cpp
M  +2    -0    plugins/dockers/animation/timeline_ruler_header.h

https://commits.kde.org/krita/c88391e465e62b43c6a23d0aca9e72ca90e01d8a

diff --git a/krita/krita.action b/krita/krita.action
index 0d219554529..3ede68c367a 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -2207,7 +2207,7 @@
 
     <Action name="remove_frames">
       <icon></icon>
-      <text>Remove Frame</text>
+      <text>Remove Keyframe</text>
       <whatsThis></whatsThis>
       <toolTip>Just remove keyframes without moving anything around</toolTip>
       <iconText></iconText>
@@ -2400,6 +2400,31 @@
       <statusTip></statusTip>
     </Action>
 
+    <Action name="mirror_frames">
+      <icon></icon>
+      <text>Mirror Frames</text>
+      <whatsThis></whatsThis>
+      <toolTip>Mirror frames' position</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
+
+    <Action name="mirror_columns">
+      <icon></icon>
+      <text>Mirror Columns</text>
+      <whatsThis></whatsThis>
+      <toolTip>Mirror columns' position</toolTip>
+      <iconText></iconText>
+      <activationFlags>100000</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut></shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
 
   </Actions>
 
diff --git a/libs/image/kis_keyframe_channel.cpp b/libs/image/kis_keyframe_channel.cpp
index 776ef195a72..cb848ad707f 100644
--- a/libs/image/kis_keyframe_channel.cpp
+++ b/libs/image/kis_keyframe_channel.cpp
@@ -173,6 +173,29 @@ bool KisKeyframeChannel::moveKeyframe(KisKeyframeSP keyframe, int newTime, KUndo
     return true;
 }
 
+bool KisKeyframeChannel::swapFrames(int lhsTime, int rhsTime, KUndo2Command *parentCommand)
+{
+    LAZY_INITIALIZE_PARENT_COMMAND(parentCommand);
+
+    if (lhsTime == rhsTime) return false;
+
+    KisKeyframeSP lhsFrame = keyframeAt(lhsTime);
+    KisKeyframeSP rhsFrame = keyframeAt(rhsTime);
+
+    if (!lhsFrame && !rhsFrame) return false;
+
+    if (lhsFrame && !rhsFrame) {
+        moveKeyframe(lhsFrame, rhsTime, parentCommand);
+    } else if (!lhsFrame && rhsFrame) {
+        moveKeyframe(rhsFrame, lhsTime, parentCommand);
+    } else {
+        KUndo2Command *cmd = new KisSwapFramesCommand(this, lhsFrame, rhsFrame, parentCommand);
+        cmd->redo();
+    }
+
+    return true;
+}
+
 bool KisKeyframeChannel::deleteKeyframeImpl(KisKeyframeSP keyframe, KUndo2Command *parentCommand, bool recreate)
 {
     LAZY_INITIALIZE_PARENT_COMMAND(parentCommand);
@@ -214,6 +237,42 @@ void KisKeyframeChannel::moveKeyframeImpl(KisKeyframeSP keyframe, int newTime)
     requestUpdate(rangeDst, rectDst);
 }
 
+void KisKeyframeChannel::swapKeyframesImpl(KisKeyframeSP lhsKeyframe, KisKeyframeSP rhsKeyframe)
+{
+    KIS_ASSERT_RECOVER_RETURN(lhsKeyframe);
+    KIS_ASSERT_RECOVER_RETURN(rhsKeyframe);
+
+    KisTimeRange rangeLhs = affectedFrames(lhsKeyframe->time());
+    KisTimeRange rangeRhs = affectedFrames(rhsKeyframe->time());
+
+    const QRect rectLhsSrc = affectedRect(lhsKeyframe);
+    const QRect rectRhsSrc = affectedRect(rhsKeyframe);
+
+    const int lhsTime = lhsKeyframe->time();
+    const int rhsTime = rhsKeyframe->time();
+
+    emit sigKeyframeAboutToBeMoved(lhsKeyframe, rhsTime);
+    emit sigKeyframeAboutToBeMoved(rhsKeyframe, lhsTime);
+
+    m_d->keys.remove(lhsTime);
+    m_d->keys.remove(rhsTime);
+
+    rhsKeyframe->setTime(lhsTime);
+    lhsKeyframe->setTime(rhsTime);
+
+    m_d->keys.insert(lhsTime, rhsKeyframe);
+    m_d->keys.insert(rhsTime, lhsKeyframe);
+
+    emit sigKeyframeMoved(lhsKeyframe, lhsTime);
+    emit sigKeyframeMoved(rhsKeyframe, rhsTime);
+
+    const QRect rectLhsDst = affectedRect(lhsKeyframe);
+    const QRect rectRhsDst = affectedRect(rhsKeyframe);
+
+    requestUpdate(rangeLhs, rectLhsSrc | rectRhsDst);
+    requestUpdate(rangeRhs, rectRhsSrc | rectLhsDst);
+}
+
 KisKeyframeSP KisKeyframeChannel::replaceKeyframeAt(int time, KisKeyframeSP newKeyframe)
 {
     Q_ASSERT(newKeyframe.isNull() || time == newKeyframe->time());
diff --git a/libs/image/kis_keyframe_channel.h b/libs/image/kis_keyframe_channel.h
index 294fde4095e..a61e7a00111 100644
--- a/libs/image/kis_keyframe_channel.h
+++ b/libs/image/kis_keyframe_channel.h
@@ -66,6 +66,7 @@ public:
     KisKeyframeSP addKeyframe(int time, KUndo2Command *parentCommand = 0);
     bool deleteKeyframe(KisKeyframeSP keyframe, KUndo2Command *parentCommand = 0);
     bool moveKeyframe(KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0);
+    bool swapFrames(int lhsTime, int rhsTime, KUndo2Command *parentCommand = 0);
     KisKeyframeSP copyKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0);
     KisKeyframeSP copyExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand = 0);
 
@@ -151,9 +152,11 @@ private:
     void removeKeyframeLogical(KisKeyframeSP keyframe);
     bool deleteKeyframeImpl(KisKeyframeSP keyframe, KUndo2Command *parentCommand, bool recreate);
     void moveKeyframeImpl(KisKeyframeSP keyframe, int newTime);
+    void swapKeyframesImpl(KisKeyframeSP lhsKeyframe, KisKeyframeSP rhsKeyframe);
 
     friend class KisMoveFrameCommand;
     friend class KisReplaceKeyframeCommand;
+    friend class KisSwapFramesCommand;
 
 private:
     KisKeyframeSP insertKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand);
diff --git a/libs/image/kis_keyframe_commands.cpp b/libs/image/kis_keyframe_commands.cpp
index 27f9d4fdb33..3030d70392a 100644
--- a/libs/image/kis_keyframe_commands.cpp
+++ b/libs/image/kis_keyframe_commands.cpp
@@ -35,3 +35,21 @@ void KisMoveFrameCommand::redo() {
 void KisMoveFrameCommand::undo() {
     m_channel->moveKeyframeImpl(m_keyframe, m_oldTime);
 }
+
+KisSwapFramesCommand::KisSwapFramesCommand(KisKeyframeChannel *channel, KisKeyframeSP lhsFrame, KisKeyframeSP rhsFrame, KUndo2Command *parentCommand)
+    : KUndo2Command(parentCommand),
+      m_channel(channel),
+      m_lhsFrame(lhsFrame),
+      m_rhsFrame(rhsFrame)
+{
+}
+
+void KisSwapFramesCommand::redo()
+{
+    m_channel->swapKeyframesImpl(m_lhsFrame, m_rhsFrame);
+}
+
+void KisSwapFramesCommand::undo()
+{
+    m_channel->swapKeyframesImpl(m_lhsFrame, m_rhsFrame);
+}
diff --git a/libs/image/kis_keyframe_commands.h b/libs/image/kis_keyframe_commands.h
index 288ef21f515..b8d4b591449 100644
--- a/libs/image/kis_keyframe_commands.h
+++ b/libs/image/kis_keyframe_commands.h
@@ -54,4 +54,18 @@ private:
     int m_newTime;
 };
 
+class KRITAIMAGE_EXPORT KisSwapFramesCommand : public KUndo2Command
+{
+public:
+    KisSwapFramesCommand(KisKeyframeChannel *channel, KisKeyframeSP lhsFrame, KisKeyframeSP rhsFrame, KUndo2Command *parentCommand);
+
+    void redo() override;
+    void undo() override;
+
+private:
+    KisKeyframeChannel *m_channel;
+    KisKeyframeSP m_lhsFrame;
+    KisKeyframeSP m_rhsFrame;
+};
+
 #endif
diff --git a/plugins/dockers/animation/kis_time_based_item_model.cpp b/plugins/dockers/animation/kis_time_based_item_model.cpp
index 163f0774191..a80693258fd 100644
--- a/plugins/dockers/animation/kis_time_based_item_model.cpp
+++ b/plugins/dockers/animation/kis_time_based_item_model.cpp
@@ -32,6 +32,7 @@
 #include "kis_processing_applicator.h"
 #include "KisImageBarrierLockerWithFeedback.h"
 #include "commands_new/kis_switch_current_time_command.h"
+#include "kis_command_utils.h"
 
 struct KisTimeBasedItemModel::Private
 {
@@ -373,6 +374,55 @@ bool KisTimeBasedItemModel::removeFramesAndOffset(QModelIndexList indexes)
     return true;
 }
 
+bool KisTimeBasedItemModel::mirrorFrames(QModelIndexList indexes)
+{
+    QScopedPointer<KUndo2Command> parentCommand(new KUndo2Command(kundo2_i18n("Mirror Frames")));
+
+    {
+        KisImageBarrierLockerWithFeedback locker(m_d->image);
+
+        QMap<int, QModelIndexList> rowsList;
+
+        Q_FOREACH (const QModelIndex &index, indexes) {
+            rowsList[index.row()].append(index);
+        }
+
+
+        Q_FOREACH (int row, rowsList.keys()) {
+            QModelIndexList &list = rowsList[row];
+
+            KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!list.isEmpty(), false);
+
+            std::sort(list.begin(), list.end(),
+                [] (const QModelIndex &lhs, const QModelIndex &rhs) {
+                    return lhs.column() < rhs.column();
+                });
+
+            auto srcIt = list.begin();
+            auto dstIt = list.end();
+
+            KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(srcIt != dstIt, false);
+            --dstIt;
+
+            QList<KisKeyframeChannel*> channels = channelsAt(*srcIt).values();
+
+            while (srcIt < dstIt) {
+                Q_FOREACH (KisKeyframeChannel *channel, channels) {
+                    channel->swapFrames(srcIt->column(), dstIt->column(), parentCommand.data());
+                }
+
+                srcIt++;
+                dstIt--;
+            }
+        }
+    }
+
+    KisProcessingApplicator::runSingleCommandStroke(m_d->image,
+                                                    new KisCommandUtils::SkipFirstRedoWrapper(parentCommand.take()),
+                                                    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 a4f169f84ee..c52ea05c324 100644
--- a/plugins/dockers/animation/kis_time_based_item_model.h
+++ b/plugins/dockers/animation/kis_time_based_item_model.h
@@ -56,6 +56,8 @@ public:
 
     bool removeFramesAndOffset(QModelIndexList indexes);
 
+    bool mirrorFrames(QModelIndexList indexes);
+
     void setScrubState(bool active);
     void scrubTo(int time, bool preview);
 
diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp
index 60131fd3467..0f3448d57a8 100644
--- a/plugins/dockers/animation/timeline_frames_view.cpp
+++ b/plugins/dockers/animation/timeline_frames_view.cpp
@@ -170,6 +170,8 @@ TimelineFramesView::TimelineFramesView(QWidget *parent)
     connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumnsCustom()), SLOT(slotInsertHoldColumnsCustom()));
     connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumnsCustom()), SLOT(slotRemoveHoldColumnsCustom()));
 
+    connect(m_d->horizontalRuler, SIGNAL(sigMirrorColumns()), SLOT(slotMirrorColumns()));
+
     m_d->layersHeader = new TimelineLayersHeader(this);
 
     m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed);
@@ -331,6 +333,9 @@ void TimelineFramesView::setActionManager( KisActionManager * actionManager)
 
         action = m_d->actionMan->createAction("remove_n_hold_frames");
         connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFramesCustom()));
+
+        action = m_d->actionMan->createAction("mirror_frames");
+        connect(action, SIGNAL(triggered()), SLOT(slotMirrorFrames()));
     }
 }
 
@@ -1036,6 +1041,9 @@ void TimelineFramesView::mousePressEvent(QMouseEvent *event)
             addActionToMenu(&menu, "insert_n_hold_frames");
             addActionToMenu(&menu, "remove_n_hold_frames");
 
+            menu.addSeparator();
+            addActionToMenu(&menu, "mirror_frames");
+
             if (haveFrames) {
                 menu.addSeparator();
                 menu.addAction(m_d->multiframeColorSelectorAction);
@@ -1197,7 +1205,7 @@ void TimelineFramesView::slotCopyFrame()
     m_d->model->copyFrame(index);
 }
 
-void TimelineFramesView::calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet<int> &rows)
+void TimelineFramesView::calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet<int> &rows) const
 {
     minColumn = std::numeric_limits<int>::max();
     maxColumn = std::numeric_limits<int>::min();
@@ -1328,7 +1336,7 @@ void TimelineFramesView::slotInsertColumnsRightCustom()
     }
 }
 
-void TimelineFramesView::slotRemoveFrame(bool forceEntireColumn, bool needsOffset)
+QModelIndexList TimelineFramesView::calculateSelectionSpan(bool forceEntireColumn) const
 {
     QModelIndexList indexes;
 
@@ -1355,6 +1363,13 @@ void TimelineFramesView::slotRemoveFrame(bool forceEntireColumn, bool needsOffse
         }
     }
 
+    return indexes;
+}
+
+void TimelineFramesView::slotRemoveFrame(bool forceEntireColumn, bool needsOffset)
+{
+    const QModelIndexList indexes = calculateSelectionSpan(forceEntireColumn);
+
     if (!indexes.isEmpty()) {
         if (needsOffset) {
             m_d->model->removeFramesAndOffset(indexes);
@@ -1480,6 +1495,20 @@ void TimelineFramesView::slotRemoveHoldColumnsCustom()
     }
 }
 
+void TimelineFramesView::slotMirrorFrames(bool forceEntireColumn)
+{
+    const QModelIndexList indexes = calculateSelectionSpan(forceEntireColumn);
+
+    if (!indexes.isEmpty()) {
+        m_d->model->mirrorFrames(indexes);
+    }
+}
+
+void TimelineFramesView::slotMirrorColumns()
+{
+    slotMirrorFrames(true);
+}
+
 int TimelineFramesView::defaultNumberOfFramesToAdd() const
 {
     KConfigGroup cfg =  KSharedConfig::openConfig()->group("FrameActionsDefaultValues");
diff --git a/plugins/dockers/animation/timeline_frames_view.h b/plugins/dockers/animation/timeline_frames_view.h
index 813819d7d17..919de0c8424 100644
--- a/plugins/dockers/animation/timeline_frames_view.h
+++ b/plugins/dockers/animation/timeline_frames_view.h
@@ -92,6 +92,9 @@ private Q_SLOTS:
     void slotInsertHoldColumnsCustom();
     void slotRemoveHoldColumnsCustom();
 
+    void slotMirrorFrames(bool forceEntireColumn = false);
+    void slotMirrorColumns();
+
     void slotReselectCurrentIndex();
 
     void slotUpdateInfiniteFramesCount();
@@ -113,13 +116,15 @@ private Q_SLOTS:
 
 private:
     void setFramesPerSecond(int fps);
-    void calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet<int> &rows);
+    void calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet<int> &rows) const;
 
     void createFrameEditingMenu();
 
     KisAction* addActionToMenu(QMenu *menu, const QString &actionId);
     void insertFramesImpl(int insertAtColumn, int count, QSet<int> rows, bool forceEntireColumn);
 
+    QModelIndexList calculateSelectionSpan(bool forceEntireColumn) const;
+
     int defaultNumberOfFramesToAdd() const;
     void setDefaultNumberOfFramesToAdd(int value) const;
 
diff --git a/plugins/dockers/animation/timeline_ruler_header.cpp b/plugins/dockers/animation/timeline_ruler_header.cpp
index 6d0297308ac..6ad35f957ef 100644
--- a/plugins/dockers/animation/timeline_ruler_header.cpp
+++ b/plugins/dockers/animation/timeline_ruler_header.cpp
@@ -102,6 +102,9 @@ void TimelineRulerHeader::setActionManager( KisActionManager * actionManager)
 
         action = actionManager->createAction("remove_n_hold_columns");
         connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveHoldColumnsCustom()));
+
+        action = actionManager->createAction("mirror_columns");
+        connect(action, SIGNAL(triggered()), SIGNAL(sigMirrorColumns()));
     }
 }
 
@@ -453,6 +456,11 @@ void TimelineRulerHeader::mousePressEvent(QMouseEvent *e)
             addActionToMenu(&menu, "insert_n_hold_columns");
             addActionToMenu(&menu, "remove_n_hold_columns");
 
+            if (numSelectedColumns > 1) {
+                menu.addSeparator();
+                addActionToMenu(&menu, "mirror_columns");
+            }
+
             menu.exec(e->globalPos());
 
             return;
diff --git a/plugins/dockers/animation/timeline_ruler_header.h b/plugins/dockers/animation/timeline_ruler_header.h
index 1b5d15aa5ae..09dd082dd7f 100644
--- a/plugins/dockers/animation/timeline_ruler_header.h
+++ b/plugins/dockers/animation/timeline_ruler_header.h
@@ -74,6 +74,8 @@ Q_SIGNALS:
     void sigInsertHoldColumnsCustom();
     void sigRemoveHoldColumnsCustom();
 
+    void sigMirrorColumns();
+
 private:
     struct Private;
     const QScopedPointer<Private> m_d;


More information about the kimageshop mailing list