[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