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);
337 Q_EMIT dataChanged(this->index(row, 0), this->index(row, columnCount() - 1));
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()->createThumbnailForFrame(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 Q_EMIT dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1));
450 Q_EMIT dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1));
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 << 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
847{
848 Q_UNUSED(dstRow);
849
851 m_d->converter->otherLayersList();
852
853 if (index < 0 || index >= list.size()) return false;
854
855 list[index].dummy->node()->setPinnedToTimeline(true);
856 dstRow = m_d->converter->rowForDummy(list[index].dummy);
857 setData(this->index(dstRow, 0), true, ActiveLayerRole);
858
859 return true;
860}
861
863{
864 return m_d->activeLayerIndex;
865}
866
868{
869 QList<QPair<int,int>> selectedCells;
870
871 Q_FOREACH(const QModelIndex &index, dstIndex){
872 if (!index.isValid()) continue;
873 selectedCells.append(QPair<int,int>(index.row(), index.column()));
874 }
875
876 if (selectedCells.size() == 0) {
877 return false;
878 }
879
880 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Add blank frame", "Add %1 blank frames", selectedCells.size()));
881
882 Q_FOREACH (auto &cell, selectedCells) {
883 KisNodeDummy *dummy = m_d->converter->dummyFromRow(cell.first);
884 if (!dummy) continue;
885
886 KisNodeSP node = dummy->node();
887 if (!KisAnimUtils::supportsContentFrames(node)) continue;
888
889 KisAnimUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Raster.id(), cell.second, false, parentCommand);
890 }
891
895
896 return true;
897}
898
899bool KisAnimTimelineFramesModel::copyFrame(const QModelIndex &dstIndex)
900{
901 if (!dstIndex.isValid()) return false;
902
903 return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true);
904}
905
907{
909
910 Q_FOREACH (const QModelIndex &index, indices) {
911 const int time = index.column();
913 if (!channel) continue;
914 frameItems << KisAnimUtils::FrameItem(channel->node(), channel->id(), time);
915 }
916
917 KisAnimUtils::makeClonesUnique(m_d->image, frameItems);
918}
919
920bool KisAnimTimelineFramesModel::insertFrames(int dstColumn, const QList<int> &dstRows, int count, int timing)
921{
922 if (dstRows.isEmpty() || count <= 0) return true;
923 timing = qMax(timing, 1);
924
925 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count));
926
927 {
928 KisImageBarrierLock locker(m_d->image);
929
930 QModelIndexList indexes;
931
932 Q_FOREACH (int row, dstRows) {
933 for (int column = dstColumn; column < columnCount(); column++) {
934 indexes << index(row, column);
935 }
936 }
937
938 setLastVisibleFrame(columnCount() + (count * timing) - 1);
939
940 createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, false, parentCommand);
941
942 Q_FOREACH (int row, dstRows) {
943 KisNodeDummy *dummy = m_d->converter->dummyFromRow(row);
944 if (!dummy) continue;
945
946 KisNodeSP node = dummy->node();
947 if (!KisAnimUtils::supportsContentFrames(node)) continue;
948
949 for (int column = dstColumn; column < dstColumn + (count * timing); column += timing) {
950 KisAnimUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Raster.id(), column, false, parentCommand);
951 }
952 }
953
954 const int oldTime = m_d->image->animationInterface()->currentUITime();
955 const int newTime = dstColumn > oldTime ? dstColumn : dstColumn + (count * timing) - 1;
956
957 new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(),
958 oldTime,
959 newTime, parentCommand);
960 }
961
965
966 return true;
967}
968
969bool KisAnimTimelineFramesModel::insertHoldFrames(const QModelIndexList &selectedIndexes, int insertCount)
970{
971 if (selectedIndexes.isEmpty() || insertCount == 0) return true;
972
973 QScopedPointer<KUndo2Command> parentCommand(new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", insertCount)));
974
975 {
976 KisImageBarrierLock locker(m_d->image);
977
978 // Find Keyframes in selection...
979 QSet<TimelineSelectionEntry> uniqueKeyframesInSelection;
980 int earliestAffectedTime = std::numeric_limits<int>::max();
981
982 Q_FOREACH (const QModelIndex &index, selectedIndexes) {
983 KisNodeSP node = nodeAt(index);
984 KIS_SAFE_ASSERT_RECOVER(node) { continue; }
985
987 if (!channel) continue;
988
989 earliestAffectedTime = qMin(earliestAffectedTime, index.column()); // Earliest of selection... (to be continued.)
990
991 int time = channel->activeKeyframeTime(index.column());
992 KisRasterKeyframeSP keyframe = channel->activeKeyframeAt<KisRasterKeyframe>(index.column());
993
994 if (keyframe) {
995 uniqueKeyframesInSelection.insert(TimelineSelectionEntry{channel, time, keyframe});
996 }
997 }
998
999 // Determine which keyframes need to move and sort by time...
1000 QList<TimelineSelectionEntry> keyframesToMove;
1001
1002 for (auto it = uniqueKeyframesInSelection.begin(); it != uniqueKeyframesInSelection.end(); ++it) {
1003 TimelineSelectionEntry keyframeEntry = *it;
1004
1005 KisRasterKeyframeChannel *channel = keyframeEntry.channel;
1006 int nextKeyframeTime = channel->nextKeyframeTime(keyframeEntry.time);
1007 KisRasterKeyframeSP nextKeyframe = channel->keyframeAt<KisRasterKeyframe>(nextKeyframeTime);
1008
1009 if (nextKeyframe) {
1010 keyframesToMove << TimelineSelectionEntry{ channel, nextKeyframeTime, nextKeyframe };
1011 }
1012 }
1013
1014 std::sort(keyframesToMove.begin(), keyframesToMove.end(),
1016 return lhs.time > rhs.time;
1017 });
1018
1019 if (keyframesToMove.isEmpty()) return true;
1020
1021 const int oldTime = m_d->image->animationInterface()->currentUITime();
1022 const int maxColumn = columnCount();
1023
1024 // Store the original active keyframe, so we can switch to its new time at the end.
1025 // (This helps to keep artist drawing on the right keyframe even while adding and removing hold frames.)
1026 QModelIndex activeIndex = index(m_d->activeLayerIndex, oldTime);
1027 KisNodeSP activeNode = nodeAt(activeIndex);
1029
1030 KisRasterKeyframeSP originalActiveKeyframe = nullptr;
1031 if (activeChannel) {
1032 originalActiveKeyframe = activeChannel->activeKeyframeAt<KisRasterKeyframe>(oldTime);
1033 }
1034
1035 // Create keyframe movement commands...
1036 if (insertCount > 0) {
1037 setLastVisibleFrame(columnCount() + insertCount);
1038 }
1039
1040 Q_FOREACH (TimelineSelectionEntry entry, keyframesToMove) {
1041 int plannedFrameMove = insertCount;
1042
1043 if (insertCount < 0) {
1045
1046 int prevKeyframeTime = entry.channel->previousKeyframeTime(entry.time);
1047 KisRasterKeyframeSP prevFrame = entry.channel->keyframeAt<KisRasterKeyframe>(prevKeyframeTime);
1048 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(prevFrame, false);
1049
1050 // Clamp values so that they never exceed or overlap the previous frame
1051 plannedFrameMove = qMax(insertCount, prevKeyframeTime - entry.time + 1);
1052
1053 earliestAffectedTime = qMin(earliestAffectedTime, prevKeyframeTime); // No longer limited to selection.
1054 }
1055
1056 KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(entry.channel->node());
1057 KIS_SAFE_ASSERT_RECOVER(dummy) { continue; }
1058
1059 const int row = m_d->converter->rowForDummy(dummy);
1060 KIS_SAFE_ASSERT_RECOVER(row >= 0) { continue; }
1061
1062 QModelIndexList indices;
1063 for (int column = entry.time; column < maxColumn; column++) {
1064 indices << index(row, column);
1065 }
1066
1068 QPoint(plannedFrameMove, 0),
1069 false,
1070 true,
1071 parentCommand.data());
1072 }
1073
1074 if (originalActiveKeyframe) {
1075 new KisSwitchCurrentTimeToKeyframeCommand(m_d->image->animationInterface(),
1076 oldTime,
1077 activeNode,
1079 originalActiveKeyframe,
1080 parentCommand.data());
1081 }
1082 }
1083
1084 KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand.take(),
1087 return true;
1088}
1089
1091{
1092 if (document()) {
1094 if (files.count() > 0) {
1095 return files.first().baseName();
1096 }
1097 }
1098 return QString("");
1099}
1100
1102{
1104
1105 QVector<QFileInfo> tracks;
1106 if (fileName.exists()) {
1107 tracks << fileName;
1108 }
1109
1110 document()->setAudioTracks(tracks);
1111}
1112
1114{
1115 return KisPart::instance()->playbackEngine()->isMute();
1116}
1117
1123
1125{
1126 if (document()) {
1127 return document()->getAudioLevel();
1128 } else {
1129 return 1.0;
1130 }
1131}
1132
1138
1140{
1141 m_d->image->animationInterface()->setDocumentRangeStartFrame(column);
1142}
1143
1145{
1146 m_d->image->animationInterface()->setDocumentRangeEndFrame(column);
1147}
1148
1150{
1151 m_d->image->animationInterface()->invalidateFrames(KisTimeSpan::infinite(0), m_d->image->bounds());
1152}
1153
1155{
1156 if (!m_d->image) return;
1157
1158 m_d->image->animationInterface()->setActiveLayerSelectedTimes(times);
1159}
float value(const T *src, size_t ch)
void decodeBaseIndex(QByteArray *encoded, int *row, int *col)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
@ 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)
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:483
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
virtual QImage createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode=Qt::IgnoreAspectRatio)
KisProjectionLeafSP projectionLeaf
Definition kis_node.cpp:93
KisRasterKeyframeChannel * channel