[krita] /: Implement copy-paste of the animation frames
Dmitry Kazakov
null at kde.org
Fri Apr 13 18:07:43 UTC 2018
Git commit 0808a2108026edf667d6ab916426b6918275fea6 by Dmitry Kazakov.
Committed on 13/04/2018 at 18:07.
Pushed by dkazakov into branch 'master'.
Implement copy-paste of the animation frames
Now one can copy-paste frames and columns on the timeline using
a special context menu.
CC:kimageshop at kde.org
M +78 -0 krita/krita.action
M +34 -7 plugins/dockers/animation/timeline_frames_model.cpp
M +12 -0 plugins/dockers/animation/timeline_frames_model.h
M +105 -3 plugins/dockers/animation/timeline_frames_view.cpp
M +11 -1 plugins/dockers/animation/timeline_frames_view.h
M +13 -0 plugins/dockers/animation/timeline_ruler_header.cpp
M +4 -0 plugins/dockers/animation/timeline_ruler_header.h
https://commits.kde.org/krita/0808a2108026edf667d6ab916426b6918275fea6
diff --git a/krita/krita.action b/krita/krita.action
index 3ede68c367a..55a465f0580 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -2426,6 +2426,84 @@
<statusTip></statusTip>
</Action>
+ <Action name="copy_frames_to_clipboard">
+ <icon></icon>
+ <text>Copy to Clipboard</text>
+ <whatsThis></whatsThis>
+ <toolTip>Copy frames to clipboard</toolTip>
+ <iconText></iconText>
+ <activationFlags>100000</activationFlags>
+ <activationConditions>0</activationConditions>
+ <shortcut></shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
+
+ <Action name="cut_frames_to_clipboard">
+ <icon></icon>
+ <text>Cut to Clipboard</text>
+ <whatsThis></whatsThis>
+ <toolTip>Cut frames to clipboard</toolTip>
+ <iconText></iconText>
+ <activationFlags>100000</activationFlags>
+ <activationConditions>0</activationConditions>
+ <shortcut></shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
+
+ <Action name="paste_frames_from_clipboard">
+ <icon></icon>
+ <text>Paste from Clipboard</text>
+ <whatsThis></whatsThis>
+ <toolTip>Paste frames from clipboard</toolTip>
+ <iconText></iconText>
+ <activationFlags>100000</activationFlags>
+ <activationConditions>0</activationConditions>
+ <shortcut></shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
+
+ <Action name="copy_columns_to_clipboard">
+ <icon></icon>
+ <text>Copy Columns to Clipboard</text>
+ <whatsThis></whatsThis>
+ <toolTip>Copy columns to clipboard</toolTip>
+ <iconText></iconText>
+ <activationFlags>100000</activationFlags>
+ <activationConditions>0</activationConditions>
+ <shortcut></shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
+
+ <Action name="cut_columns_to_clipboard">
+ <icon></icon>
+ <text>Cut Columns to Clipboard</text>
+ <whatsThis></whatsThis>
+ <toolTip>Cut columns to clipboard</toolTip>
+ <iconText></iconText>
+ <activationFlags>100000</activationFlags>
+ <activationConditions>0</activationConditions>
+ <shortcut></shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
+
+ <Action name="paste_columns_from_clipboard">
+ <icon></icon>
+ <text>Paste Columns from Clipboard</text>
+ <whatsThis></whatsThis>
+ <toolTip>Paste columns from clipboard</toolTip>
+ <iconText></iconText>
+ <activationFlags>100000</activationFlags>
+ <activationConditions>0</activationConditions>
+ <shortcut></shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
+
</Actions>
diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp
index f98e1473393..8bfa6cd8693 100644
--- a/plugins/dockers/animation/timeline_frames_model.cpp
+++ b/plugins/dockers/animation/timeline_frames_model.cpp
@@ -537,14 +537,21 @@ void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index)
}
QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const
+{
+ return mimeDataExtended(indexes, m_d->lastClickedIndex, UndefinedPolicy);
+}
+
+QMimeData *TimelineFramesModel::mimeDataExtended(const QModelIndexList &indexes,
+ const QModelIndex &baseIndex,
+ TimelineFramesModel::MimeCopyPolicy copyPolicy) const
{
QMimeData *data = new QMimeData();
QByteArray encoded;
QDataStream stream(&encoded, QIODevice::WriteOnly);
- const int baseRow = m_d->lastClickedIndex.row();
- const int baseColumn = m_d->lastClickedIndex.column();
+ const int baseRow = baseIndex.row();
+ const int baseColumn = baseIndex.column();
stream << indexes.size();
stream << baseRow << baseColumn;
@@ -553,6 +560,7 @@ QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const
stream << index.row() - baseRow << index.column() - baseColumn;
}
+ stream << int(copyPolicy);
data->setData("application/x-krita-frame", encoded);
return data;
@@ -582,17 +590,19 @@ bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction act
Q_UNUSED(row);
Q_UNUSED(column);
- bool result = false;
+ return dropMimeDataExtended(data, action, parent);
+}
- if ((action != Qt::MoveAction &&
- action != Qt::CopyAction) || !parent.isValid()) return result;
+bool TimelineFramesModel::dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved)
+{
+ bool result = false;
- const bool copyFrames = action == Qt::CopyAction;
+ if ((action != Qt::MoveAction && action != Qt::CopyAction) ||
+ !parent.isValid()) return result;
QByteArray encoded = data->data("application/x-krita-frame");
QDataStream stream(&encoded, QIODevice::ReadOnly);
-
int size, baseRow, baseColumn;
stream >> size >> baseRow >> baseColumn;
@@ -608,6 +618,23 @@ bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction act
srcIndexes << index(srcRow, srcColumn);
}
+ MimeCopyPolicy copyPolicy = UndefinedPolicy;
+
+ if (!stream.atEnd()) {
+ int value = 0;
+ stream >> value;
+ copyPolicy = MimeCopyPolicy(value);
+ }
+
+ const bool copyFrames =
+ copyPolicy == UndefinedPolicy ?
+ action == Qt::CopyAction :
+ copyPolicy == CopyFramesPolicy;
+
+ if (dataMoved) {
+ *dataMoved = !copyFrames;
+ }
+
const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow);
return offsetFrames(srcIndexes, offset, copyFrames);
diff --git a/plugins/dockers/animation/timeline_frames_model.h b/plugins/dockers/animation/timeline_frames_model.h
index 0f053c29587..7c93cd05dca 100644
--- a/plugins/dockers/animation/timeline_frames_model.h
+++ b/plugins/dockers/animation/timeline_frames_model.h
@@ -36,6 +36,14 @@ class KisAnimationPlayer;
class KRITAANIMATIONDOCKER_EXPORT TimelineFramesModel : public TimelineNodeListKeeper::ModelWithExternalNotifications
{
Q_OBJECT
+
+public:
+ enum MimeCopyPolicy {
+ UndefinedPolicy = 0,
+ MoveFramesPolicy,
+ CopyFramesPolicy
+ };
+
public:
TimelineFramesModel(QObject *parent);
~TimelineFramesModel() override;
@@ -76,7 +84,11 @@ public:
Qt::DropActions supportedDropActions() const override;
QStringList mimeTypes() const override;
QMimeData * mimeData(const QModelIndexList &indexes) const override;
+ QMimeData * mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, MimeCopyPolicy copyPolicy) const;
bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
+
+ bool dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved = 0);
+
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool insertRows(int row, int count, const QModelIndex &parent) override;
diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp
index 0f3448d57a8..6c5f2b3c788 100644
--- a/plugins/dockers/animation/timeline_frames_view.cpp
+++ b/plugins/dockers/animation/timeline_frames_view.cpp
@@ -65,6 +65,9 @@
#include <QWidgetAction>
#include <QInputDialog>
+#include <QClipboard>
+#include <QMimeData>
+
#include "config-qtmultimedia.h"
typedef QPair<QRect, QModelIndex> QItemViewPaintPair;
@@ -172,6 +175,10 @@ TimelineFramesView::TimelineFramesView(QWidget *parent)
connect(m_d->horizontalRuler, SIGNAL(sigMirrorColumns()), SLOT(slotMirrorColumns()));
+ connect(m_d->horizontalRuler, SIGNAL(sigCopyColumns()), SLOT(slotCopyColumns()));
+ connect(m_d->horizontalRuler, SIGNAL(sigCutColumns()), SLOT(slotCutColumns()));
+ connect(m_d->horizontalRuler, SIGNAL(sigPasteColumns()), SLOT(slotPasteColumns()));
+
m_d->layersHeader = new TimelineLayersHeader(this);
m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed);
@@ -336,6 +343,16 @@ void TimelineFramesView::setActionManager( KisActionManager * actionManager)
action = m_d->actionMan->createAction("mirror_frames");
connect(action, SIGNAL(triggered()), SLOT(slotMirrorFrames()));
+
+ action = m_d->actionMan->createAction("copy_frames_to_clipboard");
+ connect(action, SIGNAL(triggered()), SLOT(slotCopyFrames()));
+
+ action = m_d->actionMan->createAction("cut_frames_to_clipboard");
+ connect(action, SIGNAL(triggered()), SLOT(slotCutFrames()));
+
+ action = m_d->actionMan->createAction("paste_frames_from_clipboard");
+ connect(action, SIGNAL(triggered()), SLOT(slotPasteFrames()));
+
}
}
@@ -952,6 +969,10 @@ void TimelineFramesView::mousePressEvent(QMouseEvent *event)
}
QMenu menu;
+ addActionToMenu(&menu, "cut_frames_to_clipboard");
+ addActionToMenu(&menu, "copy_frames_to_clipboard");
+ addActionToMenu(&menu, "paste_frames_from_clipboard");
+ menu.addSeparator();
addActionToMenu(&menu, "insert_keyframes_right");
addActionToMenu(&menu, "insert_keyframes_left");
menu.addSeparator();
@@ -979,6 +1000,10 @@ void TimelineFramesView::mousePressEvent(QMouseEvent *event)
}
QMenu menu;
+ addActionToMenu(&menu, "cut_frames_to_clipboard");
+ addActionToMenu(&menu, "copy_frames_to_clipboard");
+ addActionToMenu(&menu, "paste_frames_from_clipboard");
+ menu.addSeparator();
addActionToMenu(&menu, "add_blank_frame");
addActionToMenu(&menu, "add_duplicate_frame");
menu.addSeparator();
@@ -1023,6 +1048,10 @@ void TimelineFramesView::mousePressEvent(QMouseEvent *event)
}
QMenu menu;
+ addActionToMenu(&menu, "cut_frames_to_clipboard");
+ addActionToMenu(&menu, "copy_frames_to_clipboard");
+ addActionToMenu(&menu, "paste_frames_from_clipboard");
+ menu.addSeparator();
addActionToMenu(&menu, "insert_keyframes_right");
addActionToMenu(&menu, "insert_keyframes_left");
menu.addSeparator();
@@ -1336,7 +1365,7 @@ void TimelineFramesView::slotInsertColumnsRightCustom()
}
}
-QModelIndexList TimelineFramesView::calculateSelectionSpan(bool forceEntireColumn) const
+QModelIndexList TimelineFramesView::calculateSelectionSpan(bool forceEntireColumn, bool editableOnly) const
{
QModelIndexList indexes;
@@ -1349,7 +1378,8 @@ QModelIndexList TimelineFramesView::calculateSelectionSpan(bool forceEntireColum
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;
+ if (editableOnly &&
+ !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);
@@ -1357,7 +1387,7 @@ QModelIndexList TimelineFramesView::calculateSelectionSpan(bool forceEntireColum
}
} else {
Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) {
- if (m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) {
+ if (!editableOnly || m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) {
indexes << index;
}
}
@@ -1509,6 +1539,78 @@ void TimelineFramesView::slotMirrorColumns()
slotMirrorFrames(true);
}
+void TimelineFramesView::cutCopyImpl(bool forceEntireColumn, bool copy)
+{
+ const QModelIndexList indexes = calculateSelectionSpan(forceEntireColumn, !copy);
+ if (indexes.isEmpty()) return;
+
+ int minColumn = std::numeric_limits<int>::max();
+ int minRow = std::numeric_limits<int>::max();
+
+ Q_FOREACH (const QModelIndex &index, indexes) {
+ minRow = qMin(minRow, index.row());
+ minColumn = qMin(minColumn, index.column());
+ }
+
+ const QModelIndex baseIndex = m_d->model->index(minRow, minColumn);
+
+ QMimeData *data = m_d->model->mimeDataExtended(indexes,
+ baseIndex,
+ copy ?
+ TimelineFramesModel::CopyFramesPolicy :
+ TimelineFramesModel::MoveFramesPolicy);
+ if (data) {
+ QClipboard *cb = QApplication::clipboard();
+ cb->setMimeData(data);
+ }
+}
+
+void TimelineFramesView::slotCopyFrames(bool forceEntireColumn)
+{
+ cutCopyImpl(forceEntireColumn, true);
+}
+
+void TimelineFramesView::slotCutFrames(bool forceEntireColumn)
+{
+ cutCopyImpl(forceEntireColumn, false);
+}
+
+void TimelineFramesView::slotPasteFrames(bool forceEntireColumn)
+{
+ const QModelIndex currentIndex =
+ !forceEntireColumn ? this->currentIndex() : m_d->model->index(0, this->currentIndex().column());
+
+ if (!currentIndex.isValid()) return;
+
+ QClipboard *cb = QApplication::clipboard();
+ const QMimeData *data = cb->mimeData();
+
+ if (data && data->hasFormat("application/x-krita-frame")) {
+
+ bool dataMoved = false;
+ bool result = m_d->model->dropMimeDataExtended(data, Qt::MoveAction, currentIndex, &dataMoved);
+
+ if (result && dataMoved) {
+ cb->clear();
+ }
+ }
+}
+
+void TimelineFramesView::slotCutColumns()
+{
+ slotCutFrames(true);
+}
+
+void TimelineFramesView::slotPasteColumns()
+{
+ slotPasteFrames(true);
+}
+
+void TimelineFramesView::slotCopyColumns()
+{
+ slotCopyFrames(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 919de0c8424..f1b1bbfa719 100644
--- a/plugins/dockers/animation/timeline_frames_view.h
+++ b/plugins/dockers/animation/timeline_frames_view.h
@@ -95,6 +95,15 @@ private Q_SLOTS:
void slotMirrorFrames(bool forceEntireColumn = false);
void slotMirrorColumns();
+
+ void slotCopyFrames(bool forceEntireColumn = false);
+ void slotCutFrames(bool forceEntireColumn = false);
+ void slotPasteFrames(bool forceEntireColumn = false);
+
+ void slotCopyColumns();
+ void slotCutColumns();
+ void slotPasteColumns();
+
void slotReselectCurrentIndex();
void slotUpdateInfiniteFramesCount();
@@ -123,7 +132,8 @@ private:
KisAction* addActionToMenu(QMenu *menu, const QString &actionId);
void insertFramesImpl(int insertAtColumn, int count, QSet<int> rows, bool forceEntireColumn);
- QModelIndexList calculateSelectionSpan(bool forceEntireColumn) const;
+ QModelIndexList calculateSelectionSpan(bool forceEntireColumn, bool editableOnly = true) const;
+ void cutCopyImpl(bool forceEntireColumn, bool copy);
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 6ad35f957ef..4fd60fe4bd3 100644
--- a/plugins/dockers/animation/timeline_ruler_header.cpp
+++ b/plugins/dockers/animation/timeline_ruler_header.cpp
@@ -105,6 +105,15 @@ void TimelineRulerHeader::setActionManager( KisActionManager * actionManager)
action = actionManager->createAction("mirror_columns");
connect(action, SIGNAL(triggered()), SIGNAL(sigMirrorColumns()));
+
+ action = actionManager->createAction("copy_columns_to_clipboard");
+ connect(action, SIGNAL(triggered()), SIGNAL(sigCopyColumns()));
+
+ action = actionManager->createAction("cut_columns_to_clipboard");
+ connect(action, SIGNAL(triggered()), SIGNAL(sigCutColumns()));
+
+ action = actionManager->createAction("paste_columns_from_clipboard");
+ connect(action, SIGNAL(triggered()), SIGNAL(sigPasteColumns()));
}
}
@@ -441,6 +450,10 @@ void TimelineRulerHeader::mousePressEvent(QMouseEvent *e)
}
QMenu menu;
+ addActionToMenu(&menu, "cut_columns_to_clipboard");
+ addActionToMenu(&menu, "copy_columns_to_clipboard");
+ addActionToMenu(&menu, "paste_columns_from_clipboard");
+ menu.addSeparator();
addActionToMenu(&menu, "insert_columns_right");
addActionToMenu(&menu, "insert_columns_left");
menu.addSeparator();
diff --git a/plugins/dockers/animation/timeline_ruler_header.h b/plugins/dockers/animation/timeline_ruler_header.h
index 09dd082dd7f..3dec7b98e4f 100644
--- a/plugins/dockers/animation/timeline_ruler_header.h
+++ b/plugins/dockers/animation/timeline_ruler_header.h
@@ -76,6 +76,10 @@ Q_SIGNALS:
void sigMirrorColumns();
+ void sigCutColumns();
+ void sigCopyColumns();
+ void sigPasteColumns();
+
private:
struct Private;
const QScopedPointer<Private> m_d;
More information about the kimageshop
mailing list