Krita Source Code Documentation
Loading...
Searching...
No Matches
KisAnimTimelineFramesModel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8#include <QFont>
9#include <QSize>
10#include <QColor>
11#include <QMimeData>
12#include <QPointer>
13#include <QPair>
14#include <KisResourceModel.h>
15
16#include "kis_layer.h"
17#include "kis_config.h"
18
19#include "kis_global.h"
20#include "kis_debug.h"
21#include "kis_image.h"
23#include "kis_undo_adapter.h"
31#include "kundo2command.h"
35
36#include "KisAnimUtils.h"
38#include "KisPlaybackEngine.h"
39#include "kis_node_model.h"
40#include "kis_projection_leaf.h"
41#include "kis_time_span.h"
42
44#include <kis_painting_tweaks.h>
45#include "KisPart.h"
46#include <QApplication>
47#include "KisDocument.h"
48#include "KisViewManager.h"
50#include <KisImageBarrierLock.h>
51#include "kis_node_uuid_info.h"
52#include "KisMainWindow.h"
53
54
56{
62 updateTimer(200, KisSignalCompressor::FIRST_INACTIVE),
64 {}
65
67
72
75
77 QScopedPointer<TimelineNodeListKeeper> converter;
78
79 QScopedPointer<NodeManipulationInterface> nodeInterface;
80
81 QPersistentModelIndex lastClickedIndex;
82
83 QVariant layerName(int row) const {
84 KisNodeDummy *dummy = converter->dummyFromRow(row);
85 if (!dummy) return QVariant();
86 return dummy->node()->name();
87 }
88
89 bool layerEditable(int row) const {
90 KisNodeDummy *dummy = converter->dummyFromRow(row);
91
92 if (!dummy) return true;
93
94 if (image->isIsolatingLayer()) {
95 return dummy->node()->isIsolatedRoot() && !dummy->node()->userLocked();
96 } else {
97 return dummy->node()->visible() && !dummy->node()->userLocked();
98 }
99 }
100
101 bool frameExists(int row, int column) const {
102 KisNodeDummy *dummy = converter->dummyFromRow(row);
103
104 if (!dummy) return false;
105
107
108 return (primaryChannel && primaryChannel->keyframeAt(column));
109 }
110
111 bool frameHasContent(int row, int column) const {
112 KisNodeDummy *dummy = converter->dummyFromRow(row);
113
114 if (!dummy) return false;
115
117 if (!primaryChannel) return false;
118
119 // first check if we are a key frame
120 KisRasterKeyframeSP frame = primaryChannel->activeKeyframeAt<KisRasterKeyframe>(column);
121 if (!frame) return false;
122
123 return frame->hasContent();
124 }
125
126 bool specialKeyframeExists(int row, int column) {
127 KisNodeDummy *dummy = converter->dummyFromRow(row);
128 if (!dummy) return false;
129
130 Q_FOREACH(KisKeyframeChannel *channel, dummy->node()->keyframeChannels()) {
131 if (channel->id() != KisKeyframeChannel::Raster.id() && channel->keyframeAt(column)) {
132 return true;
133 }
134 }
135 return false;
136 }
137
138 int frameColorLabel(int row, int column) {
139 KisNodeDummy *dummy = converter->dummyFromRow(row);
140 if (!dummy) return -1;
141
143 if (!primaryChannel) return -1;
144
145 KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column);
146 if (!frame) return -1;
147
148 return frame->colorLabel();
149 }
150
151 void setFrameColorLabel(int row, int column, int color) {
152 KisNodeDummy *dummy = converter->dummyFromRow(row);
153 if (!dummy) return;
154
156 if (!primaryChannel) return;
157
158 KisKeyframeSP frame = primaryChannel->keyframeAt(column);
159 if (!frame) return;
160
161 frame->setColorLabel(color);
162 }
163
164 int layerColorLabel(int row) const {
165 KisNodeDummy *dummy = converter->dummyFromRow(row);
166 if (!dummy) return -1;
167 return dummy->node()->colorLabelIndex();
168 }
169
170 QVariant layerProperties(int row) const {
171 KisNodeDummy *dummy = converter->dummyFromRow(row);
172 if (!dummy) return QVariant();
173
174 PropertyList props = dummy->node()->sectionModelProperties();
175 return QVariant::fromValue(props);
176 }
177
178 bool setLayerProperties(int row, PropertyList props) {
179 KisNodeDummy *dummy = converter->dummyFromRow(row);
180 if (!dummy) return false;
181
182 nodeInterface->setNodeProperties(dummy->node(), image, props);
183 return true;
184 }
185
186 bool addKeyframe(int row, int column, bool copy) {
187 KisNodeDummy *dummy = converter->dummyFromRow(row);
188 if (!dummy) return false;
189
190 KisNodeSP node = dummy->node();
191 if (!KisAnimUtils::supportsContentFrames(node)) return false;
192
194 return true;
195 }
196
197 bool addNewLayer(int row) {
198 Q_UNUSED(row);
199
200 if (nodeInterface) {
201 KisLayerSP layer = nodeInterface->addPaintLayer();
202 layer->setPinnedToTimeline(true);
203 }
204
205 return true;
206 }
207
208 bool removeLayer(int row) {
209 KisNodeDummy *dummy = converter->dummyFromRow(row);
210 if (!dummy) return false;
211
212 if (nodeInterface) {
213 nodeInterface->removeNode(dummy->node());
214 }
215
216 return true;
217 }
218};
219
221 : ModelWithExternalNotifications(parent),
222 m_d(new Private)
223{
224 connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue()));
225}
226
230
232{
233 return m_d->dummiesFacade;
234}
235
237{
238 m_d->nodeInterface.reset(iface);
239}
240
242{
247 KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row());
248 return dummy ? dummy->node() : nullptr;
249}
250
251QMap<QString, KisKeyframeChannel*> KisAnimTimelineFramesModel::channelsAt(QModelIndex index) const
252{
253 KisNodeSP srcDummy = nodeAt(index);
254
255 if (!srcDummy) {
256 return {};
257 }
258
259 return srcDummy->keyframeChannels();
260}
261
262KisKeyframeChannel *KisAnimTimelineFramesModel::channelByID(QModelIndex index, const QString &id) const
263{
264 KisNodeSP srcDummy = nodeAt(index);
265
266 if (!srcDummy) {
267 return nullptr;
268 }
269
270 return srcDummy->getKeyframeChannel(id);
271}
272
274 KisImageSP image,
275 KisNodeDisplayModeAdapter *displayModeAdapter)
276{
277 KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade;
278
279 if (m_d->dummiesFacade && m_d->image) {
280 m_d->image->animationInterface()->disconnect(this);
281 m_d->image->disconnect(this);
282 m_d->dummiesFacade->disconnect(this);
283 }
284
285 m_d->image = image;
287
288 m_d->dummiesFacade = dummiesFacade;
289 m_d->converter.reset();
290
291 if (m_d->dummiesFacade) {
292 m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade, displayModeAdapter));
293 connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), this, SLOT(slotDummyChanged(KisNodeDummy*)));
294 connect(m_d->image->animationInterface(), SIGNAL(sigPlaybackRangeChanged()), this, SIGNAL(sigInfiniteTimelineUpdateNeeded()));
295 connect(m_d->image, SIGNAL(sigImageModified()), this, SLOT(slotImageContentChanged()));
296 connect(m_d->image, SIGNAL(sigIsolatedModeChanged()), this, SLOT(slotImageContentChanged()));
297 }
298
299 if (m_d->dummiesFacade != oldDummiesFacade) {
300 beginResetModel();
301 endResetModel();
302 }
303
304 if (m_d->dummiesFacade) {
306 slotCurrentTimeChanged(m_d->image->animationInterface()->currentUITime());
307 }
308}
309
311{
312 if (!m_d->updateQueue.contains(dummy)) {
313 m_d->updateQueue.append(dummy);
314 }
315 m_d->updateTimer.start();
316}
317
319{
320 if (m_d->activeLayerIndex < 0) return;
321
322 KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex);
323 if (!dummy) return;
324
325 slotDummyChanged(dummy);
326}
327
329{
330 if (!m_d->converter) return;
331
332 Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) {
333 int row = m_d->converter->rowForDummy(dummy);
334
335 if (row >= 0) {
336 Q_EMIT headerDataChanged (Qt::Vertical, row, row);
338 }
339 }
340 m_d->updateQueue.clear();
341}
342
344{
345 if (!node) {
346 m_d->activeLayerIndex = -1;
347 return;
348 }
349
350 KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node);
351 if (!dummy) {
352 // It's perfectly normal that dummyForNode returns 0; that happens
353 // when views get activated while Krita is closing down.
354 return;
355 }
356
357 int lastActiveLayerIndex = m_d->activeLayerIndex;
358 const bool prevActiveWasPinned = headerData(m_d->activeLayerIndex, Qt::Vertical, PinnedToTimelineRole).toBool();
359 m_d->converter->updateActiveDummy(dummy);
360
361 const int row = m_d->converter->rowForDummy(dummy);
362 if (row < 0) {
363 qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!";
364 }
365
366 if (row >= 0 && m_d->activeLayerIndex != row) {
367 setData(index(row, 0), true, ActiveLayerRole);
368 } else if (row >= 0 ){
370
371 // If the activeLayerIndex is considered to be the same "row", but the last one was pinned,
372 // it means that the previousActiveLayer has actually moved down the list because a new
373 // layer was inserted.
374 if (prevActiveWasPinned) {
375 lastActiveLayerIndex += 1;
376 }
377 }
378
379 // NOTE: Not in love with exposing 'selection' concepts to the model,
380 // but since this issue already existed, this is alright for now.
381 // Especially since it's just a signal...
382 requestTransferSelectionBetweenRows(lastActiveLayerIndex, m_d->activeLayerIndex);
383}
384
385int KisAnimTimelineFramesModel::rowCount(const QModelIndex &parent) const
386{
387 Q_UNUSED(parent);
388 if(!m_d->dummiesFacade) return 0;
389
390 return m_d->converter->rowCount();
391}
392
393QVariant KisAnimTimelineFramesModel::data(const QModelIndex &index, int role) const
394{
395 if(!m_d->dummiesFacade) return QVariant();
396
397 switch (role) {
398 case ActiveLayerRole: {
399 return index.row() == m_d->activeLayerIndex;
400 }
401 case FrameEditableRole: {
402 return m_d->layerEditable(index.row());
403 }
404 case FrameHasContent: {
405 return m_d->frameHasContent(index.row(), index.column());
406 }
407 case FrameExistsRole: {
408 return m_d->frameExists(index.row(), index.column());
409 }
411 return m_d->specialKeyframeExists(index.row(), index.column());
412 }
414 int label = m_d->frameColorLabel(index.row(), index.column());
415 return label > 0 ? label : QVariant();
416 }
417 case Qt::DisplayRole: {
418 return m_d->layerName(index.row());
419 }
420 case Qt::TextAlignmentRole: {
421 return QVariant(Qt::AlignHCenter | Qt::AlignVCenter);
422 }
423 case Qt::UserRole + KisAbstractResourceModel::LargeThumbnail: {
424 KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row());
425 if (!dummy) {
426 return QVariant();
427 }
428 const int maxSize = 200;
429
430 QImage image(dummy->node()->createPreferredThumbnailForFrame(maxSize, maxSize, index.column(), Qt::KeepAspectRatio));
431 return image;
432 }
433 }
434
435 return ModelWithExternalNotifications::data(index, role);
436}
437
438bool KisAnimTimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role)
439{
440 if (!index.isValid() || !m_d->dummiesFacade) return false;
441
442 switch (role) {
443 case ActiveLayerRole: {
444 if (value.toBool() &&
445 index.row() != m_d->activeLayerIndex) {
446 int prevLayer = m_d->activeLayerIndex;
447 m_d->activeLayerIndex = index.row();
448
449 emitRowDataChanged(prevLayer);
450 emitRowDataChanged(m_d->activeLayerIndex);
451
452 Q_EMIT headerDataChanged(Qt::Vertical, prevLayer, prevLayer);
453 Q_EMIT headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex);
454
455 KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex);
456 KIS_ASSERT_RECOVER(dummy) { return true; }
457
458 Q_EMIT requestCurrentNodeChanged(dummy->node());
459 Q_EMIT sigEnsureRowVisible(m_d->activeLayerIndex);
460 }
461 break;
462 }
464 m_d->setFrameColorLabel(index.row(), index.column(), value.toInt());
465 }
466 break;
467 }
468
469 return ModelWithExternalNotifications::setData(index, value, role);
470}
471
472QVariant KisAnimTimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const
473{
474 if(!m_d->dummiesFacade) return QVariant();
475
476 if (orientation == Qt::Vertical) {
477 switch (role) {
478 case ActiveLayerRole:
479 return section == m_d->activeLayerIndex;
480 case Qt::DisplayRole: {
481 QVariant value = headerData(section, orientation, Qt::ToolTipRole);
482 if (!value.isValid()) return value;
483
484 QString name = value.toString();
485 const int maxNameSize = 13;
486
487 if (name.size() > maxNameSize) {
488 name = QString("%1...").arg(name.left(maxNameSize));
489 }
490
491 return name;
492 }
493 case Qt::ForegroundRole: {
494 // WARNING: this role doesn't work for header views! Use
495 // bold font to show isolated mode instead!
496 return QVariant();
497 }
498 case Qt::FontRole: {
499 KisNodeDummy *dummy = m_d->converter->dummyFromRow(section);
500 if (!dummy) return QVariant();
501 KisNodeSP node = dummy->node();
502
503 QFont baseFont;
504 if (node->projectionLeaf()->isDroppedNode()) {
505 baseFont.setStrikeOut(true);
506 } else if (m_d->image && m_d->image->isolationRootNode() &&
507 KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) {
508 baseFont.setBold(true);
509 }
510 return baseFont;
511 }
512 case Qt::ToolTipRole: {
513 return m_d->layerName(section);
514 }
516 return QVariant::fromValue(m_d->layerProperties(section));
517 }
518 case OtherLayersRole: {
520 m_d->converter->otherLayersList();
521
522 return QVariant::fromValue(list);
523 }
525 KisNodeDummy *dummy = m_d->converter->dummyFromRow(section);
526 if (!dummy) return QVariant();
527 return dummy->node()->isPinnedToTimeline();
528 }
529 case Qt::BackgroundRole: {
530 int label = m_d->layerColorLabel(section);
531 if (label > 0) {
533 QColor color = scm.colorFromLabelIndex(label);
534 QPalette pal = qApp->palette();
535 color = KisPaintingTweaks::blendColors(color, pal.color(QPalette::Button), 0.3);
536 return QBrush(color);
537 } else {
538 return QVariant();
539 }
540 }
541 }
542 }
543
544 return ModelWithExternalNotifications::headerData(section, orientation, role);
545}
546
547bool KisAnimTimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
548{
549 if (!m_d->dummiesFacade) return false;
550
551 if (orientation == Qt::Vertical) {
552 switch (role) {
553 case ActiveLayerRole: {
554 setData(index(section, 0), value, role);
555 break;
556 }
559
560 int result = m_d->setLayerProperties(section, props);
561 Q_EMIT headerDataChanged (Qt::Vertical, section, section);
562 return result;
563 }
565 KisNodeDummy *dummy = m_d->converter->dummyFromRow(section);
566 if (!dummy) return false;
567 dummy->node()->setPinnedToTimeline(value.toBool());
568 return true;
569 }
570 }
571 }
572
573 return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role);
574}
575
577{
578 return Qt::MoveAction | Qt::CopyAction | Qt::LinkAction;
579}
580
582{
583 return Qt::MoveAction | Qt::CopyAction | Qt::LinkAction;
584}
585
587{
588 QStringList types;
589 types << QLatin1String("application/x-krita-frame");
590 return types;
591}
592
594{
595 m_d->lastClickedIndex = index;
596}
597
599{
600 return mimeDataExtended(indexes, m_d->lastClickedIndex, UndefinedPolicy);
601}
602
604 const QModelIndex &baseIndex,
606{
607 QMimeData *data = new QMimeData();
608
609 QByteArray encoded;
610 QDataStream stream(&encoded, QIODevice::WriteOnly);
611
612 const int baseRow = baseIndex.row();
613 const int baseColumn = baseIndex.column();
614
615 const QByteArray uuidDataRoot = m_d->image->root()->uuid().toRfc4122();
616 stream << int(uuidDataRoot.size());
617 stream.writeRawData(uuidDataRoot.data(), uuidDataRoot.size());
618
619 stream << int(indexes.size());
620 stream << baseRow << baseColumn;
621
622 Q_FOREACH (const QModelIndex &index, indexes) {
623 KisNodeSP node = nodeAt(index);
624 KIS_SAFE_ASSERT_RECOVER(node) { continue; }
625
626 stream << index.row() - baseRow << index.column() - baseColumn;
627
628 const QByteArray uuidData = node->uuid().toRfc4122();
629 stream << int(uuidData.size());
630 stream.writeRawData(uuidData.data(), uuidData.size());
631 }
632
633 stream << int(copyPolicy);
634 data->setData("application/x-krita-frame", encoded);
635
636 return data;
637}
638
639inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col)
640{
641 int size_UNUSED = 0;
642
643 QDataStream stream(encoded, QIODevice::ReadOnly);
644 stream >> size_UNUSED >> *row >> *col;
645}
646
647bool KisAnimTimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index)
648{
649 if (!index.isValid()) return false;
650
651 if ( !m_d->layerEditable(index.row()) ) return false;
652
657 return true;
658}
659
660bool KisAnimTimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
661{
662 Q_UNUSED(row);
663 Q_UNUSED(column);
664
665 return dropMimeDataExtended(data, action, parent);
666}
667
668bool KisAnimTimelineFramesModel::dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved)
669{
670 bool result = false;
671
672 if ((action != Qt::MoveAction && action != Qt::CopyAction && action != Qt::LinkAction) ||
673 !parent.isValid()) return result;
674
675 QByteArray encoded = data->data("application/x-krita-frame");
676 QDataStream stream(&encoded, QIODevice::ReadOnly);
677
678 int uuidLenRoot = 0;
679 stream >> uuidLenRoot;
680 QByteArray uuidDataRoot(uuidLenRoot, '\0');
681 stream.readRawData(uuidDataRoot.data(), uuidLenRoot);
682 QUuid nodeUuidRoot = QUuid::fromRfc4122(uuidDataRoot);
683
684 KisPart *partInstance = KisPart::instance();
685 QList<QPointer<KisDocument>> documents = partInstance->documents();
686
687 KisImageSP srcImage = 0;
688 Q_FOREACH(KisDocument *doc, documents) {
689 KisImageSP tmpSrcImage = doc->image();
690 if (tmpSrcImage->root()->uuid() == nodeUuidRoot) {
691 srcImage = tmpSrcImage;
692 break;
693 }
694 }
695
696 if (!srcImage) {
697 KisPart *kisPartInstance = KisPart::instance();
699 i18n("Dropped frames are not available in this Krita instance")
700 , QIcon());
701 return false;
702 }
703
704 int size, baseRow, baseColumn;
705 stream >> size >> baseRow >> baseColumn;
706
707 const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow);
709 int necessaryOffset = 0; //Necessary offset to keep move above 0, used later.
710
711 for (int i = 0; i < size; i++) {
712 int relRow, relColumn;
713 stream >> relRow >> relColumn;
714
715 const int srcRow = baseRow + relRow;
716 const int srcColumn = baseColumn + relColumn;
717
718 int uuidLen = 0;
719 stream >> uuidLen;
720 QByteArray uuidData(uuidLen, '\0');
721 stream.readRawData(uuidData.data(), uuidLen);
722 QUuid nodeUuid = QUuid::fromRfc4122(uuidData);
723
724 KisNodeSP srcNode;
725
726 if (!nodeUuid.isNull()) {
727 KisNodeUuidInfo nodeInfo(nodeUuid);
728 srcNode = nodeInfo.findNode(srcImage->root());
729 } else {
730 QModelIndex index = this->index(srcRow, srcColumn);
731 srcNode = nodeAt(index);
732 }
733
734 KIS_SAFE_ASSERT_RECOVER(srcNode) { continue; }
735
736 const QModelIndex dstRowIndex = this->index(srcRow + offset.y(), 0);
737 if (!dstRowIndex.isValid()) continue;
738
739 KisNodeSP dstNode = nodeAt(dstRowIndex);
740 KIS_SAFE_ASSERT_RECOVER(dstNode) { continue; }
741
742 Q_FOREACH (KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) {
743 KisAnimUtils::FrameItem srcItem(srcNode, channel->id(), srcColumn);
744 KisAnimUtils::FrameItem dstItem(dstNode, channel->id(), srcColumn + offset.x());
745
746 if ((srcColumn + offset.x()) * -1 > necessaryOffset ) {
747 necessaryOffset = (srcColumn + offset.x()) * -1;
748 }
749
750 frameMoves << std::make_pair(srcItem, dstItem);
751 }
752 }
753
754 MimeCopyPolicy copyPolicy = UndefinedPolicy;
755
756 if (!stream.atEnd()) {
757 int value = 0;
758 stream >> value;
759 copyPolicy = MimeCopyPolicy(value);
760 }
761
762 const bool copyFrames = copyPolicy == UndefinedPolicy ?
763 action == Qt::CopyAction :
764 copyPolicy == CopyFramesPolicy;
765
766 const bool cloneFrames = action == Qt::LinkAction || copyPolicy == CloneFramesPolicy;
767
768 if (dataMoved) {
769 *dataMoved = !copyFrames;
770 }
771
772 KUndo2Command *cmd = 0;
773
774 if (!frameMoves.isEmpty()) {
775
776 // We need to make sure that no movement ever occurs into the negative values.
777 // TODO: Probably a better way to fix this, I'm not happy with this fix.
778 if (necessaryOffset > 0) {
779 for (int i = 0; i < frameMoves.count(); i++){
780 frameMoves[i].second.time += necessaryOffset;
781 }
782 }
783
784 KisImageBarrierLock lock(m_d->image);
785
786 if (cloneFrames) {
787 cmd = KisAnimUtils::createCloneKeyframesCommand(frameMoves, nullptr);
788 } else {
789 cmd = KisAnimUtils::createMoveKeyframesCommand(frameMoves, copyFrames, false, nullptr);
790 }
791 }
792
793 if (cmd) {
797 }
798
799 return cmd;
800}
801
802Qt::ItemFlags KisAnimTimelineFramesModel::flags(const QModelIndex &index) const
803{
804 Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index);
805 if (!index.isValid()) return flags;
806
807 if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) {
808 if (data(index, FrameEditableRole).toBool()) {
809 flags |= Qt::ItemIsDragEnabled;
810 }
811 }
812
818 flags |= Qt::ItemIsDropEnabled;
819
820 return flags;
821}
822
823bool KisAnimTimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent)
824{
825 Q_UNUSED(parent);
826
827 KIS_ASSERT_RECOVER(count == 1) { return false; }
828
829 if (row < 0 || row > rowCount()) return false;
830
831 bool result = m_d->addNewLayer(row);
832 return result;
833}
834
835bool KisAnimTimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent)
836{
837 Q_UNUSED(parent);
838 KIS_ASSERT_RECOVER(count == 1) { return false; }
839
840 if (row < 0 || row >= rowCount()) return false;
841
842 bool result = m_d->removeLayer(row);
843 return result;
844}
845
846void KisAnimTimelineFramesModel::requestNodeChange(const QModelIndex& nodeIndex)
847{
848 KisNodeSP node = nodeAt(nodeIndex);
849 Q_EMIT requestCurrentNodeChanged(node);
850}
851
853{
854 Q_UNUSED(dstRow);
855
857 m_d->converter->otherLayersList();
858
859 if (index < 0 || index >= list.size()) return false;
860
861 list[index].dummy->node()->setPinnedToTimeline(true);
862 dstRow = m_d->converter->rowForDummy(list[index].dummy);
863 setData(this->index(dstRow, 0), true, ActiveLayerRole);
864
865 return true;
866}
867
869{
870 return m_d->activeLayerIndex;
871}
872
874{
875 QList<QPair<int,int>> selectedCells;
876
877 Q_FOREACH(const QModelIndex &index, dstIndex){
878 if (!index.isValid()) continue;
879 selectedCells.append(QPair<int,int>(index.row(), index.column()));
880 }
881
882 if (selectedCells.size() == 0) {
883 return false;
884 }
885
886 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Add blank frame", "Add %1 blank frames", selectedCells.size()));
887
888 Q_FOREACH (auto &cell, selectedCells) {
889 KisNodeDummy *dummy = m_d->converter->dummyFromRow(cell.first);
890 if (!dummy) continue;
891
892 KisNodeSP node = dummy->node();
893 if (!KisAnimUtils::supportsContentFrames(node)) continue;
894
895 KisAnimUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Raster.id(), cell.second, false, parentCommand);
896 }
897
901
902 return true;
903}
904
905bool KisAnimTimelineFramesModel::copyFrame(const QModelIndex &dstIndex)
906{
907 if (!dstIndex.isValid()) return false;
908
909 return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true);
910}
911
913{
915
916 Q_FOREACH (const QModelIndex &index, indices) {
917 const int time = index.column();
919 if (!channel) continue;
920 frameItems << KisAnimUtils::FrameItem(channel->node(), channel->id(), time);
921 }
922
923 KisAnimUtils::makeClonesUnique(m_d->image, frameItems);
924}
925
926bool KisAnimTimelineFramesModel::insertFrames(int dstColumn, const QList<int> &dstRows, int count, int timing)
927{
928 if (dstRows.isEmpty() || count <= 0) return true;
929 timing = qMax(timing, 1);
930
931 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count));
932
933 {
934 KisImageBarrierLock locker(m_d->image);
935
936 QModelIndexList indexes;
937
938 Q_FOREACH (int row, dstRows) {
939 for (int column = dstColumn; column < columnCount(); column++) {
940 indexes << index(row, column);
941 }
942 }
943
944 setLastVisibleFrame(columnCount() + (count * timing) - 1);
945
946 createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, false, parentCommand);
947
948 Q_FOREACH (int row, dstRows) {
949 KisNodeDummy *dummy = m_d->converter->dummyFromRow(row);
950 if (!dummy) continue;
951
952 KisNodeSP node = dummy->node();
953 if (!KisAnimUtils::supportsContentFrames(node)) continue;
954
955 for (int column = dstColumn; column < dstColumn + (count * timing); column += timing) {
956 KisAnimUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Raster.id(), column, false, parentCommand);
957 }
958 }
959
960 const int oldTime = m_d->image->animationInterface()->currentUITime();
961 const int newTime = dstColumn > oldTime ? dstColumn : dstColumn + (count * timing) - 1;
962
963 new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(),
964 oldTime,
965 newTime, parentCommand);
966 }
967
971
972 return true;
973}
974
975bool KisAnimTimelineFramesModel::insertHoldFrames(const QModelIndexList &selectedIndexes, int insertCount)
976{
977 if (selectedIndexes.isEmpty() || insertCount == 0) return true;
978
979 QScopedPointer<KUndo2Command> parentCommand(new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", insertCount)));
980
981 {
982 KisImageBarrierLock locker(m_d->image);
983
984 // Find Keyframes in selection...
985 QSet<TimelineSelectionEntry> uniqueKeyframesInSelection;
986 int earliestAffectedTime = std::numeric_limits<int>::max();
987
988 Q_FOREACH (const QModelIndex &index, selectedIndexes) {
989 KisNodeSP node = nodeAt(index);
990 KIS_SAFE_ASSERT_RECOVER(node) { continue; }
991
993 if (!channel) continue;
994
995 earliestAffectedTime = qMin(earliestAffectedTime, index.column()); // Earliest of selection... (to be continued.)
996
997 int time = channel->activeKeyframeTime(index.column());
998 KisRasterKeyframeSP keyframe = channel->activeKeyframeAt<KisRasterKeyframe>(index.column());
999
1000 if (keyframe) {
1001 uniqueKeyframesInSelection.insert(TimelineSelectionEntry{channel, time, keyframe});
1002 }
1003 }
1004
1005 // Determine which keyframes need to move and sort by time...
1006 QList<TimelineSelectionEntry> keyframesToMove;
1007
1008 for (auto it = uniqueKeyframesInSelection.begin(); it != uniqueKeyframesInSelection.end(); ++it) {
1009 TimelineSelectionEntry keyframeEntry = *it;
1010
1011 KisRasterKeyframeChannel *channel = keyframeEntry.channel;
1012 int nextKeyframeTime = channel->nextKeyframeTime(keyframeEntry.time);
1013 KisRasterKeyframeSP nextKeyframe = channel->keyframeAt<KisRasterKeyframe>(nextKeyframeTime);
1014
1015 if (nextKeyframe) {
1016 keyframesToMove << TimelineSelectionEntry{ channel, nextKeyframeTime, nextKeyframe };
1017 }
1018 }
1019
1020 std::sort(keyframesToMove.begin(), keyframesToMove.end(),
1022 return lhs.time > rhs.time;
1023 });
1024
1025 if (keyframesToMove.isEmpty()) return true;
1026
1027 const int oldTime = m_d->image->animationInterface()->currentUITime();
1028 const int maxColumn = columnCount();
1029
1030 // Store the original active keyframe, so we can switch to its new time at the end.
1031 // (This helps to keep artist drawing on the right keyframe even while adding and removing hold frames.)
1032 QModelIndex activeIndex = index(m_d->activeLayerIndex, oldTime);
1033 KisNodeSP activeNode = nodeAt(activeIndex);
1035
1036 KisRasterKeyframeSP originalActiveKeyframe = nullptr;
1037 if (activeChannel) {
1038 originalActiveKeyframe = activeChannel->activeKeyframeAt<KisRasterKeyframe>(oldTime);
1039 }
1040
1041 // Create keyframe movement commands...
1042 if (insertCount > 0) {
1043 setLastVisibleFrame(columnCount() + insertCount);
1044 }
1045
1046 Q_FOREACH (TimelineSelectionEntry entry, keyframesToMove) {
1047 int plannedFrameMove = insertCount;
1048
1049 if (insertCount < 0) {
1051
1052 int prevKeyframeTime = entry.channel->previousKeyframeTime(entry.time);
1053 KisRasterKeyframeSP prevFrame = entry.channel->keyframeAt<KisRasterKeyframe>(prevKeyframeTime);
1054 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(prevFrame, false);
1055
1056 // Clamp values so that they never exceed or overlap the previous frame
1057 plannedFrameMove = qMax(insertCount, prevKeyframeTime - entry.time + 1);
1058
1059 earliestAffectedTime = qMin(earliestAffectedTime, prevKeyframeTime); // No longer limited to selection.
1060 }
1061
1062 KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(entry.channel->node());
1063 KIS_SAFE_ASSERT_RECOVER(dummy) { continue; }
1064
1065 const int row = m_d->converter->rowForDummy(dummy);
1066 KIS_SAFE_ASSERT_RECOVER(row >= 0) { continue; }
1067
1068 QModelIndexList indices;
1069 for (int column = entry.time; column < maxColumn; column++) {
1070 indices << index(row, column);
1071 }
1072
1074 QPoint(plannedFrameMove, 0),
1075 false,
1076 true,
1077 parentCommand.data());
1078 }
1079
1080 if (originalActiveKeyframe) {
1081 new KisSwitchCurrentTimeToKeyframeCommand(m_d->image->animationInterface(),
1082 oldTime,
1083 activeNode,
1085 originalActiveKeyframe,
1086 parentCommand.data());
1087 }
1088 }
1089
1090 KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand.take(),
1093 return true;
1094}
1095
1097{
1098 if (document()) {
1100 if (files.count() > 0) {
1101 return files.first().baseName();
1102 }
1103 }
1104 return QString("");
1105}
1106
1108{
1110
1111 QVector<QFileInfo> tracks;
1112 if (fileName.exists()) {
1113 tracks << fileName;
1114 }
1115
1116 document()->setAudioTracks(tracks);
1117}
1118
1120{
1121 return KisPart::instance()->playbackEngine()->isMute();
1122}
1123
1129
1131{
1132 if (document()) {
1133 return document()->getAudioLevel();
1134 } else {
1135 return 1.0;
1136 }
1137}
1138
1144
1146{
1147 m_d->image->animationInterface()->setDocumentRangeStartFrame(column);
1148}
1149
1151{
1152 m_d->image->animationInterface()->setDocumentRangeEndFrame(column);
1153}
1154
1156{
1157 m_d->image->animationInterface()->invalidateFrames(KisTimeSpan::infinite(0), m_d->image->bounds());
1158}
1159
1161{
1162 if (!m_d->image) return;
1163
1164 m_d->image->animationInterface()->setActiveLayerSelectedTimes(times);
1165}
1166
1168{
1169 int rows = rowCount();
1170 int cols = columnCount();
1171 if (row >= 0 && row < rows && cols > 0) {
1172 Q_EMIT dataChanged(index(row, 0), index(row, cols - 1));
1173 }
1174}
float value(const T *src, size_t ch)
void decodeBaseIndex(QByteArray *encoded, int *row, int *col)
@ LargeThumbnail
A larger thumbnail for displaying in a tooltip. 200x200 or so.
Qt::DropActions supportedDragActions() const override
void requestCurrentNodeChanged(KisNodeSP node)
QMap< QString, KisKeyframeChannel * > channelsAt(QModelIndex index) const override
void setActiveLayerSelectedTimes(const QSet< int > &times)
KisNodeSP nodeAt(QModelIndex index) const override
bool canDropFrameData(const QMimeData *data, const QModelIndex &index)
void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image, KisNodeDisplayModeAdapter *displayModeAdapter)
void setAudioChannelFileName(const QFileInfo &fileName)
void requestNodeChange(const QModelIndex &nodeIndex)
bool insertRows(int row, int count, const QModelIndex &parent) override
int rowCount(const QModelIndex &parent=QModelIndex()) const override
void requestTransferSelectionBetweenRows(int rowFrom, int rowTo)
bool copyFrame(const QModelIndex &dstIndex)
QVariant data(const QModelIndex &index, int role) const override
bool createFrame(const QModelIndexList &dstIndex)
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
bool insertFrames(int dstColumn, const QList< int > &dstRows, int count, int timing=1)
const QScopedPointer< Private > m_d
bool insertOtherLayer(int index, int dstRow)
bool setData(const QModelIndex &index, const QVariant &value, int role) override
QMimeData * mimeData(const QModelIndexList &indexes) const override
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override
void slotDummyChanged(KisNodeDummy *dummy)
QStringList mimeTypes() const override
void sigEnsureRowVisible(int row)
void setLastClickedIndex(const QModelIndex &index)
QMimeData * mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, MimeCopyPolicy copyPolicy) const
Qt::DropActions supportedDropActions() const override
void setNodeManipulationInterface(NodeManipulationInterface *iface)
KisKeyframeChannel * channelByID(QModelIndex index, const QString &id) const override
bool removeRows(int row, int count, const QModelIndex &parent) override
void makeClonesUnique(const QModelIndexList &indices)
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 insertHoldFrames(const QModelIndexList &selectedIndexes, int insertCount)
KisImageSP image
void setAudioTracks(QVector< QFileInfo > f)
qreal getAudioLevel()
void setAudioVolume(qreal level)
QVector< QFileInfo > getAudioTracks() const
bool isIsolatingLayer() const
KisImageAnimationInterface * animationInterface() const
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
static const KoID Raster
int previousKeyframeTime(const int time) const
KisKeyframeSP keyframeAt(int time) const
Get a keyframe at specified time. Used primarily when the value of a given keyframe is needed.
KisKeyframeSP activeKeyframeAt(int time) const
int activeKeyframeTime(int time) const
Get the time of the active keyframe. Useful for snapping any time to that of the most recent keyframe...
int nextKeyframeTime(const int time) const
KisViewManager * viewManager
KisNodeSP node() const
static bool belongsToIsolatedGroup(KisImageSP image, KisNodeSP node, KisDummiesFacadeBase *dummiesFacade)
KisNodeSP findNode(KisNodeSP rootNode)
QColor colorFromLabelIndex(int index) const
QList< QPointer< KisDocument > > documents
Definition KisPart.cpp:108
static KisPart * instance()
Definition KisPart.cpp:131
QScopedPointer< KisPlaybackEngine > playbackEngine
Definition KisPart.cpp:111
KisMainWindow * currentMainwindow() const
Definition KisPart.cpp:459
static void runSingleCommandStroke(KisImageSP image, KUndo2Command *cmd, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
runSingleCommandStroke creates a stroke and runs cmd in it. The text() field of cmd is used as a titl...
The KisRasterKeyframeChannel is a concrete KisKeyframeChannel subclass that stores and manages KisRas...
The KisRasterKeyframe class is a concrete subclass of KisKeyframe that wraps a physical raster image ...
KUndo2Command * createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, bool moveEmptyFrames, KUndo2Command *parentCommand=0)
void setImage(KisImageWSP image)
KisDocument * document() const
int columnCount(const QModelIndex &parent=QModelIndex()) const override
static KisTimeSpan infinite(int start)
void showFloatingMessage(const QString &message, const QIcon &icon, int timeout=4500, KisFloatingMessage::Priority priority=KisFloatingMessage::Medium, int alignment=Qt::AlignCenter|Qt::TextWordWrap)
shows a floating message in the top right corner of the canvas
QString id() const
Definition KoID.cpp:63
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1)
void makeClonesUnique(KisImageSP image, const FrameItemList &frames)
void createKeyframeLazy(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy)
KUndo2Command * createKeyframeCommand(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy, KUndo2Command *parentCommand)
bool supportsContentFrames(KisNodeSP node)
KUndo2Command * createMoveKeyframesCommand(const FrameItemList &srcFrames, const FrameItemList &dstFrames, bool copy, bool moveEmpty, KUndo2Command *parentCommand)
KUndo2Command * createCloneKeyframesCommand(const FrameMovePairList &srcDstPairs, KUndo2Command *parentCommand)
QColor blendColors(const QColor &c1, const QColor &c2, qreal r1)
QScopedPointer< NodeManipulationInterface > nodeInterface
bool addKeyframe(int row, int column, bool copy)
QPointer< KisDummiesFacadeBase > dummiesFacade
QScopedPointer< TimelineNodeListKeeper > converter
bool setLayerProperties(int row, PropertyList props)
void setFrameColorLabel(int row, int column, int color)
bool frameHasContent(int row, int column) const
void setPinnedToTimeline(bool pinned)
QMap< QString, KisKeyframeChannel * > keyframeChannels
bool isIsolatedRoot() const
QUuid uuid() const
bool isPinnedToTimeline() const
KisKeyframeChannel * getKeyframeChannel(const QString &id, bool create)
int colorLabelIndex() const
bool userLocked() const
virtual PropertyList sectionModelProperties() const
QString name() const
virtual bool visible(bool recursive=false) const
QImage createPreferredThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode=Qt::IgnoreAspectRatio)
KisProjectionLeafSP projectionLeaf
Definition kis_node.cpp:93
KisRasterKeyframeChannel * channel