Krita Source Code Documentation
Loading...
Searching...
No Matches
StoryboardModel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Saurabh Kumar <saurabhk660@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "StoryboardModel.h"
8#include "StoryboardView.h"
9#include "StoryboardUtils.h"
11
12#include <QMimeData>
13
14
15#include <kis_icon.h>
17#include <kis_layer_utils.h>
18#include <kis_pointer_utils.h>
19#include <kis_group_layer.h>
21#include "kis_time_span.h"
27
28#include "kis_config.h"
29
31 : QAbstractItemModel(parent)
32 , m_freezeKeyframePositions(false)
33 , m_lockBoards(false)
34 , m_reorderingKeyframes(false)
35 , m_imageIdleWatcher(10)
36 , m_renderScheduler(new KisStoryboardThumbnailRenderScheduler(this))
37 , m_renderSchedulingCompressor(1000,KisSignalCompressor::FIRST_ACTIVE)
38{
39 connect(m_renderScheduler, SIGNAL(sigFrameCompleted(int, KisPaintDeviceSP)), this, SLOT(slotFrameRenderCompleted(int, KisPaintDeviceSP)));
40 connect(m_renderScheduler, SIGNAL(sigFrameCancelled(int)), this, SLOT(slotFrameRenderCancelled(int)));
41 connect(&m_renderSchedulingCompressor, SIGNAL(timeout()), this, SLOT(slotUpdateThumbnails()));
42 connect(&m_imageIdleWatcher, SIGNAL(startedIdleMode()), m_renderScheduler, SLOT(slotStartFrameRendering()));
43}
44
49
50QModelIndex StoryboardModel::index(int row, int column, const QModelIndex &parent) const
51{
52 if (!hasIndex(row, column, parent)) {
53 return QModelIndex();
54 }
55 if (row < 0 || row >= rowCount(parent)) {
56 return QModelIndex();
57 }
58 if (column != 0) {
59 return QModelIndex();
60 }
61
62 if (isValidBoard(parent)) {
63 StoryboardItemSP parentItem = m_items.at(parent.row());
64 QSharedPointer<StoryboardChild> childItem = parentItem->child(row);
65
66 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(childItem, QModelIndex());
67 return createIndex(row, column, childItem.data());
68 } else {
69 return createIndex(row, column, m_items.at(row).data());
70 }
71}
72
73QModelIndex StoryboardModel::parent(const QModelIndex &index) const
74{
75 if (!index.isValid()) {
76 return QModelIndex();
77 }
78
79 //no parent for 1st level node
80 StoryboardItem *childItemFirstLevel = static_cast<StoryboardItem*>(index.internalPointer());
81
82 Q_FOREACH( StoryboardItemSP item, m_items) {
83 if (item.data() == childItemFirstLevel) {
84 return QModelIndex();
85 }
86 }
87
88 //return parent only for 2nd level nodes
89 StoryboardChild *childItem = static_cast<StoryboardChild*>(index.internalPointer());
90 QSharedPointer<StoryboardItem> parentItem = childItem->parent();
91 int indexOfParent = m_items.indexOf(parentItem);
92 return createIndex(indexOfParent, 0, parentItem.data());
93}
94
95int StoryboardModel::rowCount(const QModelIndex &parent) const
96{
97 if (!parent.isValid()) {
98 return m_items.count();
99 }
100 else if (!parent.parent().isValid()) {
101 QSharedPointer<StoryboardItem> parentItem = m_items.at(parent.row());
102 return parentItem->childCount();
103 }
104 return 0; //2nd level nodes have no child
105}
106
107int StoryboardModel::columnCount(const QModelIndex &parent) const
108{
109 if (!parent.isValid()) {
110 return 1;
111 }
112 //1st level nodes have 1 column
113 if (!parent.parent().isValid()) {
114 return 1;
115 }
116 //end level nodes have no child
117 return 0;
118}
119
120QVariant StoryboardModel::data(const QModelIndex &index, int role) const
121{
122 if (!index.isValid()) {
123 return QVariant();
124 }
125
126 //return data only for the storyboardChild i.e. 2nd level nodes
127 if (isValidBoard(index)) {
128 if (role == TotalSceneDurationInFrames) {
129 int duration = this->index(StoryboardItem::DurationFrame, 0, index).data().toInt()
130 + this->index(StoryboardItem::DurationSecond, 0, index).data().toInt() * getFramesPerSecond();
131 return duration;
132 }
133 else if (role == TotalSceneDurationInSeconds) {
134 qreal duration = this->index(StoryboardItem::DurationSecond, 0, index).data().toInt()
135 + this->index(StoryboardItem::DurationFrame, 0, index).data().toInt()
137 return duration;
138 }
139 return QVariant();
140 }
141
142 if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole) {
143 QSharedPointer<StoryboardChild> child = m_items.at(index.parent().row())->child(index.row());
144 if (index.row() == StoryboardItem::FrameNumber) {
145 ThumbnailData thumbnailData = qvariant_cast<ThumbnailData>(child->data());
146 if (role == Qt::UserRole) {
147 return thumbnailData.pixmap;
148 }
149 else {
150 return thumbnailData.frameNum;
151 }
152 }
153 else if (index.row() >= StoryboardItem::Comments) {
154 CommentBox commentBox = qvariant_cast<CommentBox>(child->data());
155 if (role == Qt::UserRole) { //scroll bar position
156 return commentBox.scrollValue;
157 }
158 else {
159 return commentBox.content;
160 }
161 }
162 return child->data();
163 }
164 return QVariant();
165}
166
167bool StoryboardModel::setData(const QModelIndex & index, const QVariant & value, int role)
168{
169 if (index.isValid() && !isLocked() && (role == Qt::EditRole || role == Qt::DisplayRole)) {
170 if (isValidBoard(index)) {
171 return false;
172 }
173
174 QSharedPointer<StoryboardChild> child = m_items.at(index.parent().row())->child(index.row());
175 if (child) {
176 if (index.row() == StoryboardItem::FrameNumber && !value.canConvert<ThumbnailData>()) {
177 if (value.toInt() < 0) {
178 return false;
179 }
180 ThumbnailData thumbnailData = qvariant_cast<ThumbnailData>(child->data());
181 thumbnailData.frameNum = value.toInt();
182 child->setData(QVariant::fromValue<ThumbnailData>(thumbnailData));
183 }
184 else if (index.row() == StoryboardItem::DurationSecond ||
186
189 const int sceneStartFrame = siblingAtRow(index, StoryboardItem::FrameNumber).data().toInt();
190
191 const int secondCount = index.row() == StoryboardItem::DurationSecond ? value.toInt() : secondIndex.data().toInt();
192 const int frameCount = index.row() == StoryboardItem::DurationFrame ? value.toInt() : frameIndex.data().toInt();
193
194 // Do not allow desired scene length to be shorter than keyframes within
195 // the given scene. This prevents overwriting data that exists internal
196 // to a scene.
197 const int sceneDesiredDuration = frameCount + secondCount * getFramesPerSecond();
198 const int implicitSceneDuration = qMax(
199 qMax( sceneDesiredDuration, lastKeyframeWithin(index.parent()) - sceneStartFrame + 1 ),
200 1 );
201
202 if (value.toInt() < 0 && secondIndex.data().toInt() == 0) {
203 return false;
204 }
205 if (implicitSceneDuration == data(index.parent(), TotalSceneDurationInFrames).toInt()) {
206 return false;
207 }
208 const int fps = m_image.isValid() ? m_image->animationInterface()->framerate() : 24;
209
210 QModelIndex lastScene = index.parent();
211 QModelIndex nextScene = this->index(lastScene.row() + 1, 0);
212 changeSceneHoldLength(implicitSceneDuration, index.parent());
213 while (nextScene.isValid()) {
214 const int lastSceneStartFrame = this->index(StoryboardItem::FrameNumber, 0, lastScene).data().toInt();
215 const int lastSceneDuration = lastScene == index.parent() ? implicitSceneDuration
216 : data(lastScene, TotalSceneDurationInFrames).toInt();
217 setData( this->index(StoryboardItem::FrameNumber, 0, nextScene), lastSceneStartFrame + lastSceneDuration);
218 lastScene = nextScene;
219 nextScene = this->index(lastScene.row() + 1, 0);
220 }
221
222 StoryboardItemSP scene = m_items.at(index.parent().row());
223 QSharedPointer<StoryboardChild> durationSeconds = scene->child(secondIndex.row());
224 QSharedPointer<StoryboardChild> durationFrames = scene->child(frameIndex.row());
225 durationSeconds->setData(QVariant::fromValue<int>(implicitSceneDuration / fps));
226 durationFrames->setData(QVariant::fromValue<int>(implicitSceneDuration % fps));
227
228 // Account for new durations with auto-adjust playback range option.
229 if (m_image.isValid()) {
230 KisConfig cfg(true);
231 QModelIndex lastScene = this->index(rowCount() - 1, 0);
232 if (cfg.adaptivePlaybackRange()) {
233 int frameNum = this->index(StoryboardItem::FrameNumber, 0, lastScene).data().toInt();
234 int totalDuration = data(lastScene, TotalSceneDurationInFrames).toInt();
236 playbackRange.include(frameNum+totalDuration);
238 }
239 }
240
241 }
242 else if (index.row() >= StoryboardItem::Comments && !value.canConvert<CommentBox>()) {
243 CommentBox commentBox = qvariant_cast<CommentBox>(child->data());
244 commentBox.content = value.toString();
245 child->setData(QVariant::fromValue<CommentBox>(commentBox));
246 }
247 else {
248 child->setData(value);
249 }
250 Q_EMIT dataChanged(index, index);
252 return true;
253 }
254 }
255 return false;
256}
257
258bool StoryboardModel::setCommentScrollData(const QModelIndex & index, const QVariant & value)
259{
260 QSharedPointer<StoryboardChild> child = m_items.at(index.parent().row())->child(index.row());
261 if (child) {
262 CommentBox commentBox = qvariant_cast<CommentBox>(child->data());
263 commentBox.scrollValue = value.toInt();
264 child->setData(QVariant::fromValue<CommentBox>(commentBox));
266 return true;
267 }
268 return false;
269}
270
271bool StoryboardModel::setThumbnailPixmapData(const QModelIndex & parentIndex, const KisPaintDeviceSP & dev)
272{
273 QModelIndex index = this->index(0, 0, parentIndex);
274 QRect thumbnailRect = m_view->visualRect(parentIndex);
275 float scale = qMin(thumbnailRect.height() / (float)m_image->height(), (float)thumbnailRect.width() / m_image->width());
276
277 QImage image = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), m_image->bounds());
278 QPixmap pxmap = QPixmap::fromImage(image);
279 pxmap = pxmap.scaled((1.5)*scale*m_image->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
280
281 if (isValidBoard(index))
282 return false;
283
284 QSharedPointer<StoryboardChild> child = m_items.at(index.parent().row())->child(index.row());
285 if (child) {
286 ThumbnailData thumbnailData = qvariant_cast<ThumbnailData>(child->data());
287 thumbnailData.pixmap = pxmap;
288 child->setData(QVariant::fromValue<ThumbnailData>(thumbnailData));
289 Q_EMIT dataChanged(index, index);
290 return true;
291 }
292 return false;
293}
294
295bool StoryboardModel::updateDurationData(const QModelIndex& parentIndex)
296{
297 if (!parentIndex.isValid()) {
298 return false;
299 }
300
301 QModelIndex currentScene = parentIndex;
302 QModelIndex nextScene = index(currentScene.row() + 1, 0);
303 if (nextScene.isValid()) {
304 const int currentSceneFrame = index(StoryboardItem::FrameNumber, 0, currentScene).data().toInt();
305 const int nextSceneFrame = index(StoryboardItem::FrameNumber, 0, nextScene).data().toInt();
306 const int sceneDuration = nextSceneFrame - currentSceneFrame;
307 const int fps = getFramesPerSecond();
308
309 if (index(StoryboardItem::DurationSecond, 0, parentIndex).data().toInt() != sceneDuration / fps) {
310 setData (index (StoryboardItem::DurationSecond, 0, parentIndex), sceneDuration / fps);
311 }
312 if (index(StoryboardItem::DurationFrame, 0, parentIndex).data().toInt() != sceneDuration % fps) {
313 setData (index (StoryboardItem::DurationFrame, 0, parentIndex), sceneDuration % fps);
314 }
315 }
316
317 return true;
318}
319
320Qt::ItemFlags StoryboardModel::flags(const QModelIndex & index) const
321{
322 if(!index.isValid()) {
323 return Qt::ItemIsDropEnabled;
324 }
325
326 //1st level nodes
327 if (isValidBoard(index)) {
328 return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
329 }
330
331 //2nd level nodes
332 return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren;
333}
334
335bool StoryboardModel::insertRows(int position, int rows, const QModelIndex &parent)
336{
337 if (!parent.isValid()) {
338 if (position < 0 || position > m_items.count()) {
339 return false;
340 }
341
342 if (isLocked()) {
343 return false;
344 }
345
346 beginInsertRows(QModelIndex(), position, position+rows-1);
347 for (int row = 0; row < rows; ++row) {
349 m_items.insert(position + row, newItem);
350 }
351 endInsertRows();
353 return true;
354 }
355 else if (!parent.parent().isValid()) { //insert 2nd level nodes
356 StoryboardItemSP item = m_items.at(parent.row());
357
358 if (position < 0 || position > item->childCount()) {
359 return false;
360 }
361 beginInsertRows(parent, position, position+rows-1);
362 for (int row = 0; row < rows; ++row) {
363 item->insertChild(position, QVariant());
364 }
365 endInsertRows();
367 return true;
368 }
369 //we can't insert to 2nd level nodes as they are leaf nodes
370 return false;
371}
372
373bool StoryboardModel::removeRows(int position, int rows, const QModelIndex &parent)
374{
375 if (rows <= 0) {
376 return false;
377 }
378 //remove 1st level nodes
379 if (!parent.isValid()) {
380
381 if (position < 0 || position >= m_items.count()) {
382 return false;
383 }
384
385 if (isLocked()) {
386 return false;
387 }
388
389 beginRemoveRows(QModelIndex(), position, position+rows-1);
390
391 for (int row = position + rows - 1; row >= position; row--) {
392 m_items.removeAt(row);
393 if (m_items.count() == 0) {
394 break;
395 }
396 }
397 endRemoveRows();
399 return true;
400 }
401 else if (!parent.parent().isValid()) { //remove 2nd level nodes
402 StoryboardItemSP item = m_items.at(parent.row());
403
404 if (position < 0 || position >= item->childCount()) {
405 return false;
406 }
407 if (m_items.contains(item)) {
408 beginRemoveRows(parent, position, position+rows-1);
409 for (int row = 0; row < rows; ++row) {
410 item->removeChild(position);
411 }
412 endRemoveRows();
414 return true;
415 }
416 }
417 //2nd level node has no child
418 return false;
419}
420
421bool StoryboardModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
422{
423 KisMoveStoryboardCommand *command = new KisMoveStoryboardCommand(sourceRow, count, destinationChild, this);
424
425 if (moveRowsImpl(sourceParent, sourceRow, count, destinationParent, destinationChild, command)) {
426 if (!sourceParent.isValid()) {
427 const int sceneIndex = sourceRow < destinationChild ? destinationChild - 1 : destinationChild;
429 sceneIndex,
430 this,
431 m_image,
432 command);
433 }
434
436 return true;
437 }
438
439 return false;
440}
441
443{
444 QStringList types;
445 types << QLatin1String("application/x-krita-storyboard");
446 return types;
447}
448
449QMimeData *StoryboardModel::mimeData(const QModelIndexList &indexes) const
450{
451 QMimeData *mimeData = new QMimeData();
452 QByteArray encodeData;
453
454 QDataStream stream(&encodeData, QIODevice::WriteOnly);
455
456 //take the row number of the index where drag started
457 foreach (QModelIndex index, indexes) {
458 if (index.isValid()) {
459 int row = index.row();
460 stream << row;
461 }
462 }
463
464 mimeData->setData("application/x-krita-storyboard", encodeData); //default mimetype
465 return mimeData;
466}
467
468bool StoryboardModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
469 int row, int column, const QModelIndex &parent)
470{
471 Q_UNUSED(column);
472 if (action == Qt::IgnoreAction) {
473 return false;
474 }
475
476 if (action == Qt::MoveAction && data->hasFormat("application/x-krita-storyboard")) {
477 QByteArray bytes = data->data("application/x-krita-storyboard");
478 QDataStream stream(&bytes, QIODevice::ReadOnly);
479
480 if (parent.isValid()) {
481 return false;
482 }
483
484 if (isLocked()) {
485 return false;
486 }
487
488 int sourceRow;
489 QModelIndexList moveRowIndexes;
490 while (!stream.atEnd()) {
491 stream >> sourceRow;
492 QModelIndex index = this->index(sourceRow, 0);
493 moveRowIndexes.append(index);
494 }
495
496 moveRows(QModelIndex(), moveRowIndexes.at(0).row(), moveRowIndexes.count(), parent, row);
497 //returning true deletes the source row
498 return false;
499 }
500 return false;
501}
502
504{
505 return Qt::CopyAction | Qt::MoveAction;
506}
507
509{
510 return Qt::CopyAction | Qt::MoveAction;
511}
512
514{
515 int visibleComments = 0;
516 foreach(StoryboardComment comment, m_commentList) {
517 if (comment.visibility) {
518 visibleComments++;
519 }
520 }
521 return visibleComments;
522}
523
525{
526 return m_commentList.count();
527}
528
529
530
531int StoryboardModel::visibleCommentsUpto(QModelIndex index) const
532{
533 int commentRow = index.row() - 4;
534 int visibleComments = 0;
535 for (int row = 0; row < commentRow; row++) {
536 if (m_commentList.at(row).visibility) {
537 visibleComments++;
538 }
539 }
540 return visibleComments;
541}
542
544{
545 m_commentModel = commentModel;
546 connect(m_commentModel, SIGNAL(dataChanged(const QModelIndex ,const QModelIndex)),
547 this, SLOT(slotCommentDataChanged()));
548 connect(m_commentModel, SIGNAL(rowsRemoved(const QModelIndex ,int, int)),
549 this, SLOT(slotCommentRowRemoved(const QModelIndex ,int, int)));
550 connect(m_commentModel, SIGNAL(rowsInserted(const QModelIndex, int, int)),
551 this, SLOT(slotCommentRowInserted(const QModelIndex, int, int)));
552 connect(m_commentModel, SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)),
553 this, SLOT(slotCommentRowMoved(const QModelIndex, int, int, const QModelIndex, int)));
554}
555
557{
558 return m_commentList.at(row);
559}
560
565
567{
569}
570
575
577{
578 return m_lockBoards;
579}
580
585
587{
588 m_view = view;
589}
590
592{
593 if (m_image) {
594 m_image->disconnect(this);
595 m_image->animationInterface()->disconnect(this);
596 }
597 m_image = image;
600
601 if (!image) {
602 return;
603 }
604
605 //setting image to a different image stops rendering of all frames previously scheduled.
606 //resetData() must be called before setImage(KisImageWSP) so that we can schedule rendering for the items in the new KisDocument
607 foreach (StoryboardItemSP item, m_items) {
608 int frame = qvariant_cast<ThumbnailData>(item->child(StoryboardItem::FrameNumber)->data()).frameNum.toInt();
610 }
611 m_lastScene = m_items.size();
612
614
615 connect(m_image, SIGNAL(sigImageUpdated(const QRect &)), &m_renderSchedulingCompressor, SLOT(start()));
616
617 connect(m_image, SIGNAL(sigRemoveNodeAsync(KisNodeSP)), this, SLOT(slotNodeRemoved(KisNodeSP)));
618
619 //for add, remove and move
620 connect(m_image->animationInterface(), SIGNAL(sigKeyframeAdded(const KisKeyframeChannel*,int)),
621 this, SLOT(slotKeyframeAdded(const KisKeyframeChannel*,int)), Qt::UniqueConnection);
622 connect(m_image->animationInterface(), SIGNAL(sigKeyframeRemoved(const KisKeyframeChannel*,int)),
623 this, SLOT(slotKeyframeRemoved(const KisKeyframeChannel*,int)), Qt::UniqueConnection);
624
625 connect(m_image->animationInterface(), SIGNAL(sigFramerateChanged()), this, SLOT(slotFramerateChanged()), Qt::UniqueConnection);
626
627 //for selection sync with timeline
629 connect(m_image->animationInterface(), SIGNAL(sigUiTimeChanged(int)), this, SLOT(slotCurrentFrameChanged(int)), Qt::UniqueConnection);
630}
631
636
637QModelIndex StoryboardModel::indexFromFrame(int frame, bool framePerfect) const
638{
639 int end = rowCount(), begin = 0;
640 while (end >= begin) {
641 const int row = begin + (end - begin) / 2;
642 const int nextRow = row + 1;
643 QModelIndex parentIndex = index(row, 0);
644 QModelIndex frameNumIndex = index(StoryboardItem::FrameNumber, 0, parentIndex);
645 QModelIndex nextParentIndex = index(nextRow, 0);
646 QModelIndex nextFrameNumIndex = index(StoryboardItem::FrameNumber, 0, nextParentIndex);
647
648 if (framePerfect && frame == frameNumIndex.data().toInt()) {
649 return parentIndex;
650 } else if (frame >= frameNumIndex.data().toInt() && (!nextParentIndex.isValid() || frame < nextFrameNumIndex.data().toInt())) {
651 return parentIndex;
652 } else if (frame < frameNumIndex.data().toInt()) {
653 end = row - 1;
654 } else if (frame > frameNumIndex.data().toInt()) {
655 begin = row + 1;
656 }
657 }
658
659 return QModelIndex();
660}
661
662QModelIndex StoryboardModel::lastIndexBeforeFrame(int frame) const
663{
664 return indexFromFrame(frame, false);
665}
666
668{
669 QModelIndex firstIndex = index(0,0);
670
671 if (!firstIndex.isValid())
672 return QModelIndexList();
673
674 if ( range.start() > index(StoryboardItem::FrameNumber, 0, firstIndex).data().toInt()) {
675 firstIndex = indexFromFrame(range.start(), false);
676 }
677
678 QModelIndex lastIndex = index(rowCount() - 1, 0);
679
680 if (!range.isInfinite() && range.isValid()) {
681 lastIndex = indexFromFrame(range.end(), false);
682 }
683
684 QItemSelectionRange indexRange(firstIndex, lastIndex);
685 return indexRange.indexes();
686}
687
688int StoryboardModel::nextKeyframeGlobal(int keyframeTime) const
689{
690 KisNodeSP node = m_image->rootLayer();
691 int nextKeyframeTime = INT_MAX;
692 if (node) {
693 KisLayerUtils::recursiveApplyNodes (node, [keyframeTime, &nextKeyframeTime] (KisNodeSP node)
694 {
696 KisKeyframeChannel* channel = node->getKeyframeChannel(KisKeyframeChannel::Raster.id(), false);
697
698 if (!channel) {
699 return;
700 }
701
702 int nextKeyframeTimeQuery = channel->nextKeyframeTime(keyframeTime);
703 if (channel->keyframeAt(nextKeyframeTimeQuery)) {
704 if (nextKeyframeTime == INT_MAX) {
705 nextKeyframeTime = nextKeyframeTimeQuery;
706 } else {
707 nextKeyframeTime = qMin(nextKeyframeTime, nextKeyframeTimeQuery);
708 }
709 }
710 }
711 });
712 }
713
714 return nextKeyframeTime;
715}
716
718{
719 if (!m_image)
720 return 0;
721
722 KisNodeSP node = m_image->rootLayer();
723 int lastKeyframeTime = 0;
724 if (node) {
725 KisLayerUtils::recursiveApplyNodes (node, [&lastKeyframeTime] (KisNodeSP node)
726 {
728 KisKeyframeChannel* channel = node->getKeyframeChannel(KisKeyframeChannel::Raster.id(), false);
729
730 if (!channel) {
731 return;
732 }
733
734 lastKeyframeTime = qMax(channel->lastKeyframeTime(), lastKeyframeTime);
735 }
736 });
737 }
738
739 return lastKeyframeTime;
740}
741
742int StoryboardModel::lastKeyframeWithin(QModelIndex sceneIndex)
743{
744 KIS_ASSERT(sceneIndex.isValid());
745 const int sceneFrame = index(StoryboardItem::FrameNumber, 0, sceneIndex).data().toInt();
746
747 if (!m_image)
748 return sceneFrame;
749
750 QModelIndex nextScene = index(sceneIndex.row() + 1, 0);
751 int nextSceneFrame;
752 if (nextScene.isValid()) {
753 nextSceneFrame = data(index(StoryboardItem::FrameNumber, 0, nextScene)).toInt();
754 }
755 else {
756 nextSceneFrame = sceneFrame + data(sceneIndex, TotalSceneDurationInFrames).toInt();
757 }
758
759 int lastFrameOfScene = sceneFrame;
760 for (int frame = sceneFrame; frame < nextSceneFrame; frame = nextKeyframeGlobal(frame)) {
761 lastFrameOfScene = frame;
762 }
763
764 return lastFrameOfScene;
765}
766
768{
769 //Get the earliest frame number in the storyboard list
770 int earliestFrame = INT_MAX;
771
772 if (!m_image) {
773 return;
774 }
775
776 typedef int AssociateFrameOffset;
777
778 QMultiHash<QModelIndex, AssociateFrameOffset> frameAssociates;
779
780 for (int i = 0; i < rowCount(); i++) {
781 QModelIndex sceneIndex = index(i, 0);
782 int sceneFrame = index(StoryboardItem::FrameNumber, 0, sceneIndex).data().toInt();
783 earliestFrame = sceneFrame < earliestFrame ? sceneFrame : earliestFrame;
784
785 const int lastFrameOfScene = index(StoryboardItem::FrameNumber, 0, sceneIndex).data().toInt()
786 + data(sceneIndex, TotalSceneDurationInFrames).toInt();
787
788 for( int i = sceneFrame; i < lastFrameOfScene; i++) {
789 frameAssociates.insert(sceneIndex, i - sceneFrame);
790 }
791 }
792
793 if (earliestFrame == INT_MAX) {
794 return;
795 }
796
797 //We want to temporarily lock responding to keyframe removal / addition.
798 //Will unlock when scope exits.
799 QScopedPointer<KeyframeReorderLock> lock(new KeyframeReorderLock(this));
800
801 //Let's cancel all frame rendering for the time being.
803
804 KisNodeSP root = m_image->root();
805 if (root && !m_freezeKeyframePositions) {
806 KisLayerUtils::recursiveApplyNodes(root, [this, earliestFrame, frameAssociates](KisNodeSP node) {
807 if (!node->isAnimated() || !node->paintDevice())
808 return;
809
811
812 if (!rasterChan)
813 return;
814
815 // Gather all original keyframes and their associated time values.
816 QHash<int, KisKeyframeSP> originalKeyframes;
817 Q_FOREACH( const int& time, rasterChan->allKeyframeTimes()) {
818 if (time >= earliestFrame && rasterChan->keyframeAt(time)) {
819 originalKeyframes.insert(time, rasterChan->keyframeAt(time));
820 rasterChan->removeKeyframe(time);
821 }
822 }
823
824 //Now lets re-sort the raster channels...
825 int intendedSceneFrameTime = earliestFrame;
826 for (int i = 0; i < rowCount(); i++) {
827 QModelIndex sceneIndex = index(i, 0);
828 const int srcFrame = index(StoryboardItem::FrameNumber, 0, sceneIndex).data().toInt();
829 Q_FOREACH( const int& associateFrameOffset, frameAssociates.values(sceneIndex) ) {
830 if (!originalKeyframes.contains(srcFrame + associateFrameOffset))
831 continue;
832
833 rasterChan->insertKeyframe(intendedSceneFrameTime + associateFrameOffset,
834 originalKeyframes.value(srcFrame + associateFrameOffset));
835 }
836
837 intendedSceneFrameTime += data(sceneIndex, TotalSceneDurationInFrames).toInt();
838 }
839 });
840 }
841
842 //Lastly, let's update all of the frame values.
843 int intendedFrameValue = earliestFrame;
844 for (int i = 0; i < rowCount(); i++) {
845 QModelIndex sceneIndex = index(i, 0);
846 setData(index(StoryboardItem::FrameNumber, 0, sceneIndex), intendedFrameValue);
847 slotUpdateThumbnailForFrame(intendedFrameValue);
848 intendedFrameValue += data(sceneIndex, TotalSceneDurationInFrames).toInt();
849 }
850
852}
853
854bool StoryboardModel::changeSceneHoldLength(int newDuration, QModelIndex itemIndex)
855{
856 if (!itemIndex.isValid()) {
857 return false;
858 }
859
860 const int origSceneFrameLength = data(itemIndex, TotalSceneDurationInFrames).toInt();
861 const int lastFrameOfScene = lastKeyframeWithin(itemIndex);
862
863 int durationChange = newDuration - origSceneFrameLength;
864 if (durationChange == 0) {
865 return false;
866 }
867
868 if (origSceneFrameLength != 0) {
869 shiftKeyframes(KisTimeSpan::infinite(lastFrameOfScene + 1), durationChange);
870 }
871
872 return true;
873}
874
876 if (!m_image)
877 return;
878
879 KisNodeSP node = m_image->rootLayer();
880
881 if (offset == 0)
882 return;
883
884 //We want to temporarily lock respondance to keyframe removal / addition.
885 //Will unlock when scope exits.
886 QScopedPointer<KeyframeReorderLock> lock(new KeyframeReorderLock(this));
887
888 if (node && !m_freezeKeyframePositions) {
889 KisLayerUtils::recursiveApplyNodes (node, [affected, offset, cmd] (KisNodeSP node) {
890 const int startFrame = affected.start();
891 if (node->isAnimated()) {
892 Q_FOREACH(KisKeyframeChannel* keyframeChannel, node->keyframeChannels()) {
893 if (keyframeChannel) {
894 if (offset > 0) {
895 int timeIter = affected.isInfinite() ?
896 keyframeChannel->lastKeyframeTime()
897 : keyframeChannel->activeKeyframeTime(affected.end());
898
899 KisKeyframeSP iterEnd = keyframeChannel->keyframeAt(keyframeChannel->previousKeyframeTime(startFrame));
900
901 while (keyframeChannel->keyframeAt(timeIter) &&
902 keyframeChannel->keyframeAt(timeIter) != iterEnd) {
903 keyframeChannel->moveKeyframe(timeIter, timeIter + offset, cmd);
904 timeIter = keyframeChannel->previousKeyframeTime(timeIter);
905 }
906
907 } else {
908 int timeIter = keyframeChannel->keyframeAt(startFrame) ? startFrame : keyframeChannel->nextKeyframeTime(startFrame);
909
910 KisKeyframeSP iterEnd = affected.isInfinite() ?
911 nullptr
912 : keyframeChannel->keyframeAt(keyframeChannel->nextKeyframeTime(affected.end()));
913
914 while (keyframeChannel->keyframeAt(timeIter) != iterEnd) {
915 keyframeChannel->moveKeyframe(timeIter, timeIter + offset, cmd);
916 timeIter = keyframeChannel->nextKeyframeTime(timeIter);
917 }
918 }
919 }
920 }
921 }
922 });
923 }
924}
925
926bool StoryboardModel::insertItem(QModelIndex index, bool after)
927{
928 //index is the index at which context menu was created, or the + button belongs to
929 //after is whether we want the item before or after index
930
931 //disable for vector layers
932 if (!m_activeNode->paintDevice()) {
933 return false;
934 }
935
936 int desiredIndex;
937 if (!index.isValid()) {
938 desiredIndex = rowCount();
939 } else {
940 desiredIndex = after ? index.row() + 1 : index.row();
941 }
942
943 insertRow(desiredIndex);
944 KisAddStoryboardCommand *command = new KisAddStoryboardCommand(desiredIndex, m_items.at(desiredIndex), this);
945 insertChildRows(desiredIndex, command);
946 const int currentTime = m_image->animationInterface()->currentTime();
947 const int desiredTime = this->index(StoryboardItem::FrameNumber, 0, this->index(desiredIndex, 0)).data().toInt();
948
949 if (m_image && currentTime != desiredTime) {
951 currentTime,
952 desiredTime,
953 command);
954 switchTimeCmd->redo();
955 } else {
956 m_view->setCurrentItem(currentTime);
957 }
958
959 pushUndoCommand(command);
960
961 // Let's start rendering after adding new storyboard items.
964
965 return true;
966}
967
968bool StoryboardModel::removeItem(QModelIndex index, KUndo2Command *command)
969 {
970 const int row = index.row();
971 const int durationDeletedScene = data(index, TotalSceneDurationInFrames).toInt();
972
973 //remove all keyframes within the scene with command as parent
974 KisNodeSP node = m_image->rootLayer();
975 const int firstFrameOfScene = data(this->index(StoryboardItem::FrameNumber, 0, index)).toInt();
976 const int timeOfNextScene = firstFrameOfScene + durationDeletedScene;
977 if (command) {
978 if (node) {
979 KisLayerUtils::recursiveApplyNodes (node, [firstFrameOfScene, timeOfNextScene, command] (KisNodeSP node){
980 if (node->isAnimated() && node->isEditable(true)) {
981 Q_FOREACH(KisKeyframeChannel* keyframeChannel, node->keyframeChannels()) {
982 int timeIter = keyframeChannel->keyframeAt(firstFrameOfScene)
983 ? firstFrameOfScene
984 : keyframeChannel->nextKeyframeTime(firstFrameOfScene);
985
986 while (keyframeChannel->keyframeAt(timeIter) && timeIter < timeOfNextScene) {
987 keyframeChannel->removeKeyframe(timeIter, command);
988 timeIter = keyframeChannel->nextKeyframeTime(timeIter);
989 }
990 }
991 }
992 });
993 }
994 shiftKeyframes(KisTimeSpan::infinite(timeOfNextScene), -durationDeletedScene, command);
995
996 //If we're viewing the scene we're about the remove, let's jump back to the last valid scene.
997 if (row > 0 && row <= rowCount()) {
998 QModelIndex currentSceneFN = this->index(StoryboardItem::FrameNumber, 0, this->index(row, 0));
999 if (m_image && m_image->animationInterface()->currentTime() == currentSceneFN.data().toInt()) {
1001 currentSceneFN.data().toInt(),
1002 this->index(StoryboardItem::FrameNumber, 0, this->index(row-1, 0)).data().toInt(),
1003 command);
1004 switchTimeCmd->redo();
1005 }
1006 }
1007 }
1008
1009 removeRows(row, 1);
1010 for (int i = row; i < rowCount(); i++) {
1011 QModelIndex frameNumIndex = this->index(StoryboardItem::FrameNumber, 0, this->index(i, 0));
1012 setData(frameNumIndex, data(frameNumIndex).toInt() - durationDeletedScene);
1013 }
1014
1015 //do we also need to make the 'change frame' command a child??
1016 // Render after removing
1017 slotUpdateThumbnails();
1018 m_renderScheduler->slotStartFrameRendering();
1019
1020 return true;
1021}
1022
1024{
1025 beginResetModel();
1026 m_items = list;
1027 endResetModel();
1028}
1029
1034
1039
1041{
1042 m_view->setCurrentItem(frameId);
1043}
1044
1046{
1048 return;
1049
1050 const QModelIndex lastScene = lastIndexBeforeFrame(time);
1051 const QModelIndex nextScene = index( lastScene.row() + 1, 0);
1052 const bool extendsLastScene = lastScene.isValid() && !nextScene.isValid();
1053
1054 //Capture new keyframes after last scene and extend duration to include the new key.
1055 if (extendsLastScene) {
1056 const int sceneStartFrame = index(StoryboardItem::FrameNumber, 0, lastScene).data().toInt();
1057 const int desiredDuration = time - sceneStartFrame + 1;
1058 const int actualDuration = data(lastScene, TotalSceneDurationInFrames).toInt();
1059 const int duration = qMax(actualDuration, desiredDuration);
1060 KIS_ASSERT(duration > 0);
1061 const QSharedPointer<StoryboardChild> frameElement = m_items.at(lastScene.row())->child(StoryboardItem::DurationFrame);
1062 const QSharedPointer<StoryboardChild> secondElement = m_items.at(lastScene.row())->child(StoryboardItem::DurationSecond);
1063 frameElement->setData(QVariant::fromValue<int>(duration % getFramesPerSecond()));
1064 secondElement->setData(QVariant::fromValue<int>(duration / getFramesPerSecond()));
1065
1066 Q_EMIT dataChanged(lastScene, lastScene);
1067 }
1068
1071}
1072
1074{
1076 return;
1077
1080}
1081
1083{
1084 if (node->isAnimated() && node->paintDevice() && node->paintDevice()->keyframeChannel()) {
1085 KisKeyframeChannel *channel = node->paintDevice()->keyframeChannel();
1086 int keyframeTime = channel->firstKeyframeTime();
1087 while (channel->keyframeAt(keyframeTime)) {
1088 //sigKeyframeRemoved is not emitted when parent node is deleted so calling explicitly
1089 slotKeyframeRemoved(channel, keyframeTime);
1090 keyframeTime = channel->nextKeyframeTime(keyframeTime);
1091 }
1092 }
1093
1095}
1096
1098{
1099 QModelIndex sceneIndex = index(0,0);
1100 QModelIndex nextScene = index(1,0);
1101 if (nextScene.isValid()) {
1102
1103 while (sceneIndex.isValid() && nextScene.isValid()) {
1104 StoryboardItemSP item = m_items.at(sceneIndex.row());
1105 const int nextSceneFrame = index(StoryboardItem::FrameNumber, 0, nextScene).data().toInt();
1106 const int sceneFrame = index(StoryboardItem::FrameNumber, 0, sceneIndex).data().toInt();
1107 const int duration = nextSceneFrame - sceneFrame;
1108 const int durationFrames = duration % getFramesPerSecond();
1109 const int durationSeconds = duration / getFramesPerSecond();
1110
1111 item->child(StoryboardItem::DurationFrame)->setData(QVariant::fromValue<int>(durationFrames));
1112 item->child(StoryboardItem::DurationSecond)->setData(QVariant::fromValue<int>(durationSeconds));
1113
1114 sceneIndex = nextScene;
1115 nextScene = index(sceneIndex.row() + 1, sceneIndex.column());
1116 }
1117
1118 Q_EMIT dataChanged(index(0,0), sceneIndex);
1119 } else if (sceneIndex.isValid()) {
1120
1121 StoryboardItemSP item = m_items.at(sceneIndex.row());
1122 const int lastKeyframe = lastKeyframeGlobal();
1123 const int sceneFrame = index(StoryboardItem::FrameNumber, 0, sceneIndex).data().toInt();
1124 const int duration = lastKeyframe + 1 - sceneFrame;
1125
1126 const int durationFrames = duration % getFramesPerSecond();
1127 const int durationSeconds = duration / getFramesPerSecond();
1128
1129 item->child(StoryboardItem::DurationFrame)->setData(QVariant::fromValue<int>(durationFrames));
1130 item->child(StoryboardItem::DurationSecond)->setData(QVariant::fromValue<int>(durationSeconds));
1131
1132 Q_EMIT dataChanged(sceneIndex, sceneIndex);
1133 }
1134}
1135
1137{
1138 Q_UNUSED(delay);
1139 if (!m_image) {
1140 return;
1141 }
1142
1143 QModelIndex index = indexFromFrame(frame);
1144 bool affected = true;
1145 if (index.isValid() && !isLocked()) {
1148 }
1149}
1150
1152 if (isLocked())
1153 return;
1154
1155 Q_FOREACH( const QModelIndex& storyboardItemIndex, indices ) {
1156 if (!storyboardItemIndex.isValid())
1157 continue;
1158
1159 //If this isn't a storyboard item (root) we will continue
1160 if (storyboardItemIndex.parent().isValid())
1161 continue;
1162
1163 const int frame = index(StoryboardItem::FrameNumber, 0, storyboardItemIndex).data().toInt();
1164 slotUpdateThumbnailForFrame(frame, false);
1165 }
1166}
1167
1169{
1170 if (!m_image || isLocked()) {
1171 return;
1172 }
1173
1174 int currentTime = m_image->animationInterface()->currentUITime();
1175 slotUpdateThumbnailForFrame(currentTime);
1176
1177 KisTimeSpan affectedRange;
1178 if (m_activeNode) {
1179 affectedRange = KisTimeSpan::calculateAffectedFramesRecursive(m_activeNode, currentTime);
1180 QModelIndexList dirtyIndexes = affectedIndexes(affectedRange);
1181 foreach(QModelIndex index, dirtyIndexes) {
1182 int frame = this->index(StoryboardItem::FrameNumber, 0, index).data().toInt();
1184 }
1185 }
1186 else {
1187 return;
1188 }
1189}
1190
1192{
1193 QModelIndex index = indexFromFrame(frame);
1194 if (index.isValid()) {
1196 }
1197}
1198
1200{
1201 Q_UNUSED(frame);
1202}
1203
1205{
1207 emit(layoutChanged());
1208}
1209
1210void StoryboardModel::slotCommentRowInserted(const QModelIndex parent, int first, int last)
1211{
1212 Q_UNUSED(parent);
1213 int numItems = rowCount();
1214 for(int row = 0; row < numItems; row++) {
1215 QModelIndex parentIndex = index(row, 0);
1216 insertRows(4 + first, last - first + 1, parentIndex); //four indices are already there
1217 }
1219}
1220
1221void StoryboardModel::slotCommentRowRemoved(const QModelIndex parent, int first, int last)
1222{
1223 Q_UNUSED(parent);
1224 int numItems = rowCount();
1225 for(int row = 0; row < numItems; row++) {
1226 QModelIndex parentIndex = index(row, 0);
1227 removeRows(4 + first, last - first + 1, parentIndex);
1228 }
1230}
1231
1232void StoryboardModel::slotCommentRowMoved(const QModelIndex &sourceParent, int start, int end,
1233 const QModelIndex &destinationParent, int destinationRow)
1234{
1235 Q_UNUSED(sourceParent);
1236 Q_UNUSED(destinationParent);
1237 int numItems = rowCount();
1238 for(int row = 0; row < numItems; row++) {
1239 QModelIndex parentIndex = index(row, 0);
1240 moveRowsImpl(parentIndex, start + 4, end - start + 1, parentIndex, destinationRow + 4);
1241 }
1243}
1244
1246{
1247 if (position + 1 < rowCount()) {
1248 const int frame = index(StoryboardItem::FrameNumber, 0, index(position + 1, 0)).data().toInt();
1250 }
1251
1252 for (int row = position + 1; row < rowCount(); ++row) {
1253 const int frame = index(StoryboardItem::FrameNumber, 0, index(row, 0)).data().toInt();
1254 setData(index(StoryboardItem::FrameNumber, 0, index(row, 0)), frame + 1);
1255 }
1256
1257 QModelIndex parentIndex = index(position, 0);
1258 insertRows(0, 4 + m_commentList.count(), parentIndex);
1259
1260 m_lastScene++;
1261 QString sceneName = i18nc("default name for storyboard item", "scene ") + QString::number(m_lastScene);
1262 setData(index(StoryboardItem::ItemName, 0, parentIndex), sceneName);
1263
1264 const bool firstEntry = rowCount() == 1;
1265
1266 if (position == 0) {
1267 setData(index(StoryboardItem::FrameNumber, 0, index(position, 0)), 0);
1269 } else {
1270 const int targetFrame = index(StoryboardItem::FrameNumber, 0, index(position - 1,0)).data().toInt()
1271 + data(index(position - 1, 0), TotalSceneDurationInFrames).toInt();
1272
1273 setData(index(StoryboardItem::FrameNumber, 0, index(position, 0)), targetFrame);
1274 setData(index(StoryboardItem::DurationFrame, 0, parentIndex), 1);
1275 setData(index(StoryboardItem::DurationSecond, 0, parentIndex), 0);
1276 }
1277
1278 if (firstEntry) {
1279 createDuplicateKeyframes(index(position, 0), cmd);
1280 } else {
1281 createBlankKeyframes(index(position, 0), cmd);
1282 }
1283
1284 const int frameToSwitch = index(StoryboardItem::FrameNumber, 0, index(position, 0)).data().toInt();
1285 if (m_image) {
1287 switchFrameCmd->redo();
1288 }
1289}
1290
1291void StoryboardModel::visualizeScene(const QModelIndex &scene, bool useUndo)
1292{
1293 if (scene.parent().isValid() || !m_image) {
1294 return;
1295 }
1296
1297 int frameTime = index(StoryboardItem::FrameNumber, 0, scene).data().toInt();
1298
1299 if (frameTime != m_image->animationInterface()->currentTime()) {
1301 }
1302}
1303
1305{
1307 const int targetFrame = index(StoryboardItem::FrameNumber, 0, pIndex).data().toInt();
1308
1309 KisLayerUtils::recursiveApplyNodes(m_image->root(), [targetFrame, cmd](KisNodeSP node){
1310 if (node->supportsKeyframeChannel(KisKeyframeChannel::Raster.id())
1311 && node->isEditable(true)) {
1312
1313 KisKeyframeChannel* chan = node->getKeyframeChannel(KisKeyframeChannel::Raster.id(), true);
1314 const int activeFrameTime = chan->activeKeyframeTime(targetFrame);
1315 chan->copyKeyframe(activeFrameTime, targetFrame, cmd);
1316 }
1317 });
1318 }
1319}
1320
1321void StoryboardModel::createBlankKeyframes(const QModelIndex &pIndex, KUndo2Command *cmd)
1322{
1324 const int targetFrame = index(StoryboardItem::FrameNumber, 0, pIndex).data().toInt();
1325
1326 KisLayerUtils::recursiveApplyNodes(m_image->root(), [targetFrame, cmd](KisNodeSP node){
1327 if (node->supportsKeyframeChannel(KisKeyframeChannel::Raster.id())
1328 && node->isEditable(true)) {
1329
1330 KisKeyframeChannel* chan = node->getKeyframeChannel(KisKeyframeChannel::Raster.id(), true);
1331 chan->addKeyframe(targetFrame, cmd);
1332 }
1333 });
1334 }
1335}
1336
1337bool StoryboardModel::moveRowsImpl(const QModelIndex &sourceParent, int sourceRow, int count,
1338 const QModelIndex &destinationParent, int destinationChild, KUndo2Command* parentCMD)
1339{
1340 if (sourceParent != destinationParent) {
1341 return false;
1342 }
1343 if (destinationChild == sourceRow || destinationChild == sourceRow + 1) {
1344 return false;
1345 }
1346
1347 if (isLocked()) {
1348 return false;
1349 }
1350
1351 //We want to test-run an action when nullptrs are present.
1352 bool dryrun = (parentCMD != nullptr);
1353
1354 if (destinationChild > sourceRow + count - 1) {
1355 //we adjust for the upward shift, see qt doc for why this is needed
1356 beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild + count - 1);
1357 destinationChild = destinationChild - count;
1358 }
1359 else {
1360 beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild);
1361 }
1362 //for moves within the 1st level nodes for comment nodes
1363 if (sourceParent == destinationParent && sourceParent.isValid() && !sourceParent.parent().isValid()) {
1364 const QModelIndex parent = sourceParent;
1365 for (int row = 0; row < count; row++) {
1366 if (sourceRow < StoryboardItem::Comments || sourceRow >= rowCount(parent)) {
1367 return false;
1368 }
1369 if (destinationChild + row < StoryboardItem::Comments || destinationChild + row >= rowCount(parent)) {
1370 return false;
1371 }
1372
1373 StoryboardItemSP item = m_items.at(parent.row());
1374
1375 if (!dryrun) {
1376 item->moveChild(sourceRow, destinationChild + row);
1377 }
1378 }
1379 endMoveRows();
1380
1381 if (!dryrun) {
1384 }
1385
1386 return true;
1387 }
1388 else if (!sourceParent.isValid()) { //for moves of 1st level nodes
1389 for (int row = 0; row < count; row++) {
1390 if (sourceRow < 0 || sourceRow >= rowCount()) {
1391 return false;
1392 }
1393 if (destinationChild + row < 0 || destinationChild + row >= rowCount()) {
1394 return false;
1395 }
1396
1397 if (!dryrun) {
1398 m_items.move(sourceRow, destinationChild + row);
1399 }
1400 }
1401 endMoveRows();
1402
1403 if (!dryrun) {
1406 }
1407
1408 return true;
1409 }
1410 else {
1411 return false;
1412 }
1413}
1414
1416{
1417 QModelIndex parentIndex = index(position, 0);
1418 insertRows(0, 4 + m_commentList.count(), parentIndex);
1419
1420 setFreeze(true);
1421 for (int i = 0; i < item->childCount(); i++) {
1422 QVariant data = item->child(i)->data();
1423 setData(index(i, 0, index(position, 0)), data);
1424 }
1425 setFreeze(false);
1428}
float value(const T *src, size_t ch)
QList< QModelIndex > QModelIndexList
Definition LayerBox.h:41
QModelIndex siblingAtRow(const QModelIndex &index, int row)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
This class is a simple combination of two QVariants. It can be converted to and from QVariant type an...
QVariant scrollValue
the value of the scroll bar of the comment scrollbar
QVariant content
the text content of the Comment
bool adaptivePlaybackRange(bool defaultValue=false) const
void setTrackedImage(KisImageSP image)
void switchCurrentTimeAsync(int frameId, SwitchTimeAsyncFlags options=STAO_NONE)
void setDocumentRange(const KisTimeSpan range)
const KisTimeSpan & documentPlaybackRange() const
documentPlaybackRange
KisGroupLayerSP rootLayer() const
KisImageAnimationInterface * animationInterface() const
qint32 width() const
QSize size() const
Definition kis_image.h:547
KisPostExecutionUndoAdapter * postExecutionUndoAdapter() const override
qint32 height() const
QRect bounds() const override
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
QSet< int > allKeyframeTimes() const
Get a set of all integer times that map to a keyframe.
static const KoID Raster
KisKeyframeSP keyframeAt(int time) const
Get a keyframe at specified time. Used primarily when the value of a given keyframe is needed.
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
KisRasterKeyframeChannel * keyframeChannel() const
QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags()) const
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...
virtual void insertKeyframe(int time, KisKeyframeSP keyframe, KUndo2Command *parentUndoCmd=nullptr) override
Insert an existing keyframe into the channel at the specified time.
virtual void removeKeyframe(int time, KUndo2Command *parentUndoCmd=nullptr) override
Remove a keyframe from the channel at the specified time.
This class maintains queues of dirty frames sorted in the order of proximity to the last changed fram...
void scheduleFrameForRegeneration(int frame, bool affected)
Adds the frame to the list of "to be regenerated" frames.
void cancelAllFrameRendering()
Cancels all frame rendering. Empties all queues and cancels the current rendering,...
void setImage(KisImageSP image)
Sets an image, the class takes an image, clones it and calls frame regeneration on the clone so do no...
int start() const
bool isInfinite() const
static KisTimeSpan infinite(int start)
void include(int time)
static KisTimeSpan calculateAffectedFramesRecursive(const KisNode *node, int time)
int end() const
static KisTimeSpan fromTimeToTime(int start, int end)
bool isValid() const
bool isValid() const
QString id() const
Definition KoID.cpp:63
This class makes up the StoryboardItem class. It consists of pointer to its parent item and the data ...
StoryboardItemSP parent()
QVector< StoryboardComment > m_commentList
This class stores a list of StoryboardChild objects and provides functionality to manipulate the list...
@ DurationFrame
Store the duration in frame at index 3. Data type stored here should be int.
@ FrameNumber
Store the frame number at index 0. Data type stored here should be ThumbnailData.
@ DurationSecond
Store the duration in second at index 2. Data type stored here should be int.
@ ItemName
Store the item name at index 1. Data type stored here should be string.
@ Comments
Store the comments at indices greater_than_or_equal_to to index 4. Data type stored here should be Co...
void createBlankKeyframes(const QModelIndex &index, KUndo2Command *cmd=nullptr)
void sigStoryboardItemListChanged()
This signal is emitted whenever m_items is changed. it is used to keep the StoryboardItemList in KisD...
void insertChildRows(int position, KUndo2Command *cmd=nullptr)
must be called after a first level index is inserted. Adds child nodes to the first level indices
QStringList mimeTypes() const override
KisStoryboardThumbnailRenderScheduler * m_renderScheduler
KisIdleWatcher m_imageIdleWatcher
void slotKeyframeRemoved(const KisKeyframeChannel *channel, int time)
friend class KisMoveStoryboardCommand
void setView(StoryboardView *view)
void slotFrameRenderCompleted(int frame, KisPaintDeviceSP dev)
called KisStoryboardThumbnailRenderScheduler when frame render is complete
void slotCurrentFrameChanged(int frameId)
called when currentUiTime changes
KisNodeWSP m_activeNode
QMimeData * mimeData(const QModelIndexList &indexes) const override
StoryboardView * m_view
void setCommentModel(StoryboardCommentModel *commentModel)
Sets the commentModel in StoryboardModel and creates connections to keep the local copy of comments i...
bool setCommentScrollData(const QModelIndex &index, const QVariant &value)
Sets the scrollValue of the CommentBox object.
bool isValidBoard(const QModelIndex &index) const
int getFramesPerSecond() const
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
bool setThumbnailPixmapData(const QModelIndex &parentIndex, const KisPaintDeviceSP &dev)
Sets the Pixmap data.
bool updateDurationData(const QModelIndex &parentIndex)
updates the duration data of item at parentIndex to the number of frame to the next keyframe in any l...
void slotSetActiveNode(KisNodeSP)
void visualizeScene(const QModelIndex &index, bool useUndo=true)
void slotCommentRowInserted(const QModelIndex, int, int)
StoryboardItemList getData()
int totalCommentCount()
Get total number of comments.
bool removeRows(int position, int rows, const QModelIndex &index=QModelIndex()) override
StoryboardComment getComment(int row) const
int columnCount(const QModelIndex &parent=QModelIndex()) const override
bool isLocked() const
StoryboardCommentModel * m_commentModel
int lastKeyframeGlobal() const
QModelIndex indexFromFrame(int frame, bool framePerfect=true) const
Returns the index of the item corresponding the frame, if there is an item with that frame.
void resetData(StoryboardItemList list)
resets m_items to list
QModelIndex parent(const QModelIndex &index) const override
bool insertItem(QModelIndex index, bool after)
inserts item after or before index based on after parameter
void slotUpdateThumbnails()
calls regeneration of the currentUiTime() and all frames in affectedIndexes(KisTimeSpan)
QVector< StoryboardComment > m_commentList
void pushUndoCommand(KUndo2Command *command)
bool insertRows(int position, int rows, const QModelIndex &index=QModelIndex()) override
int lastKeyframeWithin(QModelIndex index)
Gets the last keyframe that exists within an index's duration. Used to prevent duration from overwrit...
Qt::DropActions supportedDropActions() const override
void slotUpdateThumbnailsForItems(QModelIndexList indices)
bool removeItem(QModelIndex index, KUndo2Command *command=nullptr)
removes item, deletes keyframes within and shifts keyframe after the scene to fill in the gap
bool isFrozen() const
int visibleCommentCount() const
Used in StoryboardDelegate and StoryboardView to get size of one storyboard item.
void slotCommentRowRemoved(const QModelIndex, int, int)
KisImageWSP m_image
Qt::ItemFlags flags(const QModelIndex &index) const override
int rowCount(const QModelIndex &parent=QModelIndex()) const override
int visibleCommentsUpto(QModelIndex index) const
Used in StoryboardView to design the layout of storyboard item.
QModelIndexList affectedIndexes(KisTimeSpan range) const
Returns a list of index of items that have frame in between argument range.
void reorderKeyframes()
reorders all keyframes to reflect storyboard docker's arrangement. typically used after drag and drop...
void slotUpdateThumbnailForFrame(int frame, bool delay=true)
calls regeneration of frame in the background i.e. in another thread.
StoryboardModel(QObject *parent)
bool moveRowsImpl(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild, KUndo2Command *parentCMD=nullptr)
KisSignalCompressor m_renderSchedulingCompressor
bool changeSceneHoldLength(int oldDuration, QModelIndex itemIndex)
moves all keyframes in all layers after the frame of the parent of durationIndex Keyframes are moved ...
void createDuplicateKeyframes(const QModelIndex &index, KUndo2Command *cmd=nullptr)
~StoryboardModel() override
StoryboardItemList m_items
void slotNodeRemoved(KisNodeSP node)
QModelIndex lastIndexBeforeFrame(int frame) const
Returns the index of the item with largest frame smaller than argument frame.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
int nextKeyframeGlobal(int keyframeTime) const
the next time at which there is a keyframe in any layer after keyframeTime
Qt::DropActions supportedDragActions() const override
void slotFrameRenderCancelled(int frame)
called KisStoryboardThumbnailRenderScheduler when frame render is cancelled.
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
void slotCommentRowMoved(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
void setImage(KisImageWSP image)
void shiftKeyframes(KisTimeSpan affected, int offset, KUndo2Command *cmd=nullptr)
void slotKeyframeAdded(const KisKeyframeChannel *channel, int time)
QRect visualRect(const QModelIndex &index) const override
void setCurrentItem(int frame)
changes the currentIndex and selectedIndex to frame
This class is a simple combination of two QVariants. It can be converted to and from QVariant type an...
QVariant frameNum
the frame number corresponding to this item in the timeline docker
QVariant pixmap
a scaled down thumbnail version of the frame
void encodeData(Imf::OutputFile &file, const QList< ExrPaintLayerSaveInfo > &informationObjects, int width, int height)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
QSharedPointer< T > toQShared(T *ptr)
void recursiveApplyNodes(NodePointer node, Functor func)
bool isEditable(bool checkVisibility=true) const
bool isAnimated() const
virtual KisPaintDeviceSP paintDevice() const =0
virtual bool supportsKeyframeChannel(const QString &id)
static KoColorSpaceRegistry * instance()