17#include <QApplication>
23#include <QKeySequence>
24#include <QInputDialog>
30#include "KSharedConfig"
133 : QTableView(parent),
137 m_d->modifiersCatcher->addModifier(
"pan-zoom", Qt::Key_Space);
138 m_d->modifiersCatcher->addModifier(
"offset-frame", Qt::Key_Shift);
140 setCornerButtonEnabled(
false);
141 setSelectionBehavior(QAbstractItemView::SelectItems);
142 setSelectionMode(QAbstractItemView::ExtendedSelection);
146 setDragEnabled(
true);
147 setDragDropMode(QAbstractItemView::DragDrop);
148 setAcceptDrops(
true);
149 setDropIndicatorShown(
true);
150 setDefaultDropAction(Qt::MoveAction);
153 this->setHorizontalHeader(
m_d->horizontalRuler);
178 m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed);
180 m_d->layersHeader->setDefaultSectionSize(24);
181 m_d->layersHeader->setMinimumWidth(60);
182 m_d->layersHeader->setHighlightSections(
true);
183 this->setVerticalHeader(
m_d->layersHeader);
187 m_d->layerEditingMenu =
new QMenu(
this);
188 m_d->layerEditingMenu->addSection(i18n(
"Edit Layers:"));
189 m_d->layerEditingMenu->addSeparator();
193 m_d->layerEditingMenu->addSeparator();
201 m_d->addLayersButton =
new QToolButton(
this);
202 m_d->addLayersButton->setAutoRaise(
true);
204 m_d->addLayersButton->setIconSize(QSize(22, 22));
205 m_d->addLayersButton->setPopupMode(QToolButton::InstantPopup);
206 m_d->addLayersButton->setMenu(
m_d->layerEditingMenu);
212 m_d->colorSelector->installEventFilter(clickIgnore);
213 m_d->colorSelectorAction =
new QWidgetAction(
this);
214 m_d->colorSelectorAction->setDefaultWidget(
m_d->colorSelector);
218 m_d->multiframeColorSelector->installEventFilter(clickIgnore);
219 m_d->multiframeColorSelectorAction =
new QWidgetAction(
this);
220 m_d->multiframeColorSelectorAction->setDefaultWidget(
m_d->multiframeColorSelector);
230 m_d->zoomDragButton->setAutoRaise(
true);
232 m_d->zoomDragButton->setIconSize(QSize(22, 22));
234 m_d->zoomDragButton->setToolTip(i18nc(
"@info:tooltip",
"Zoom Timeline. Hold down and drag left or right."));
235 m_d->zoomDragButton->setPopupMode(QToolButton::InstantPopup);
241 setHorizontalScrollBar(hZoomableBar);
242 setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
243 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
245 hZoomableBar->setEnabled(
false);
248 connect(hZoomableBar, SIGNAL(zoom(qreal)),
this, SLOT(
slotZoom(qreal)));
261 connect(scroller, SIGNAL(stateChanged(QScroller::State)),
264 connect(&
m_d->kineticScrollInfiniteFrameUpdater, &QTimer::timeout, [
this, scroller](){
265 slotUpdateInfiniteFramesCount();
266 scroller->resendPrepareEvent();
269 QScrollerProperties props = scroller->scrollerProperties();
270 props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
271 props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
272 scroller->setScrollerProperties(props);
276 connect(&
m_d->selectionChangedCompressor, SIGNAL(timeout()),
278 connect(&
m_d->selectionChangedCompressor, SIGNAL(timeout()),
282 QClipboard *cb = QApplication::clipboard();
296 m_d->model = framesModel;
298 QTableView::setModel(model);
300 connect(
m_d->model, SIGNAL(headerDataChanged(Qt::Orientation,
int,
int)),
303 connect(
m_d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
306 connect(
m_d->model, SIGNAL(rowsRemoved(QModelIndex,
int,
int)),
309 connect(
m_d->model, SIGNAL(sigInfiniteTimelineUpdateNeeded()),
312 connect(
m_d->model, SIGNAL(requestTransferSelectionBetweenRows(
int,
int)),
315 connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
316 &
m_d->selectionChangedCompressor, SLOT(start()));
323 m_d->actionMan = actionManager;
324 m_d->horizontalRuler->setActionManager(actionManager);
329 action =
m_d->actionMan->createAction(
"add_blank_frame");
332 action =
m_d->actionMan->createAction(
"add_duplicate_frame");
335 action =
m_d->actionMan->createAction(
"insert_keyframe_left");
338 action =
m_d->actionMan->createAction(
"insert_keyframe_right");
341 action =
m_d->actionMan->createAction(
"insert_multiple_keyframes");
344 action =
m_d->actionMan->createAction(
"remove_frames_and_pull");
347 action =
m_d->actionMan->createAction(
"remove_frames");
350 action =
m_d->actionMan->createAction(
"insert_hold_frame");
353 action =
m_d->actionMan->createAction(
"insert_multiple_hold_frames");
356 action =
m_d->actionMan->createAction(
"remove_hold_frame");
359 action =
m_d->actionMan->createAction(
"remove_multiple_hold_frames");
362 action =
m_d->actionMan->createAction(
"mirror_frames");
365 action =
m_d->actionMan->createAction(
"copy_frames");
368 action =
m_d->actionMan->createAction(
"copy_frames_as_clones");
369 connect(action, &KisAction::triggered, [
this](){
clone(
false);});
371 action =
m_d->actionMan->createAction(
"make_clones_unique");
374 action =
m_d->actionMan->createAction(
"cut_frames");
377 action =
m_d->actionMan->createAction(
"paste_frames");
380 action =
m_d->actionMan->createAction(
"set_start_time");
383 action =
m_d->actionMan->createAction(
"set_end_time");
386 action =
m_d->actionMan->createAction(
"update_playback_range");
389 action =
m_d->actionMan->actionByName(
"pin_to_timeline");
390 m_d->pinLayerToTimelineAction = action;
391 m_d->layerEditingMenu->addAction(action);
397 QTableView::updateGeometries();
399 const int availableHeight =
m_d->horizontalRuler->height();
400 const int margin = 2;
401 const int minimalSize = availableHeight - 2 * margin;
407 int y = (availableHeight - minimalSize) / 2;
408 m_d->addLayersButton->move(x, 2 * y);
410 const int availableWidth =
m_d->layersHeader->width();
412 x = availableWidth - margin - minimalSize;
413 m_d->zoomDragButton->move(x, 2 * y);
422 state->disconnect(
this);
426 m_d->canvas = canvas;
428 horizontalScrollBar()->setEnabled(
m_d->canvas !=
nullptr);
441 m_d->existingLayersMenu->clear();
444 if (
value.isValid()) {
449 action =
m_d->existingLayersMenu->addAction(l.
name);
450 action->setData(i++);
457 if (!
m_d->actionMan)
return;
460 const bool hasEditableFrames = !editableIndexes.isEmpty();
462 bool hasExistingFrames =
false;
463 Q_FOREACH (
const QModelIndex &index, editableIndexes) {
465 hasExistingFrames =
true;
470 auto enableAction = [
this] (
const QString &id,
bool value) {
473 action->setEnabled(
value);
476 enableAction(
"add_blank_frame", hasEditableFrames);
477 enableAction(
"add_duplicate_frame", hasEditableFrames);
479 enableAction(
"insert_keyframe_left", hasEditableFrames);
480 enableAction(
"insert_keyframe_right", hasEditableFrames);
481 enableAction(
"insert_multiple_keyframes", hasEditableFrames);
483 enableAction(
"remove_frames", hasEditableFrames && hasExistingFrames);
484 enableAction(
"remove_frames_and_pull", hasEditableFrames);
486 enableAction(
"insert_hold_frame", hasEditableFrames);
487 enableAction(
"insert_multiple_hold_frames", hasEditableFrames);
489 enableAction(
"remove_hold_frame", hasEditableFrames);
490 enableAction(
"remove_multiple_hold_frames", hasEditableFrames);
492 enableAction(
"mirror_frames", hasEditableFrames && editableIndexes.size() > 1);
494 enableAction(
"copy_frames",
true);
495 enableAction(
"cut_frames", hasEditableFrames);
500 int minColumn = std::numeric_limits<int>::max();
501 int maxColumn = std::numeric_limits<int>::min();
505 foreach (
const QModelIndex &idx, selectedIndexes()) {
506 if (idx.column() > maxColumn) {
507 maxColumn = idx.column();
510 if (idx.column() < minColumn) {
511 minColumn = idx.column();
516 if (maxColumn > minColumn) {
520 m_d->model->setPlaybackRange(range);
525 QModelIndex index = currentIndex();
532 QModelIndex current = model()->index(toRow,
m_d->model->currentTime());
533 if (selectedIndexes().count() <= 1) {
534 if (selectedIndexes().count() != 1 ||
535 (selectedIndexes().first().column() == current.column() &&
536 selectedIndexes().first().row() == fromRow)) {
537 setCurrentIndex(current);
545 m_d->model->setDocumentClipRangeStart(this->currentIndex().column());
550 m_d->model->setDocumentClipRangeEnd(this->currentIndex().column());
561 m_d->model->setDocumentClipRangeStart(minColumn);
562 m_d->model->setDocumentClipRangeEnd(maxColumn);
567 const int lastVisibleFrame =
m_d->horizontalRuler->estimateLastVisibleColumn();
568 m_d->model->setLastVisibleFrame(lastVisibleFrame);
573 if (
m_d->model->isPlaybackActive())
return;
575 int selectedColumn = -1;
577 for (
int j = topLeft.column(); j <= bottomRight.column(); j++) {
579 m_d->model->index(topLeft.row(), j),
588 QModelIndex index = currentIndex();
590 if (!index.isValid() && selectedColumn < 0) {
594 if (selectionModel()->selectedIndexes().count() > 1)
return;
596 if (selectedColumn == -1) {
597 selectedColumn = index.column();
600 if (selectedColumn != index.column() && !
m_d->dragInProgress && !
m_d->model->isScrubbing()) {
601 int row = index.isValid() ? index.row() : 0;
603 selectionModel()->setCurrentIndex(
m_d->model->index(row, selectedColumn), QItemSelectionModel::ClearAndSelect);
612 if (orientation == Qt::Horizontal) {
615 if (newFps !=
m_d->fps) {
625 Q_FOREACH(QModelIndex index, selectedIndexes()) {
633 QModelIndex index = currentIndex();
634 const int newRow = index.isValid() ? index.row() : 0;
635 model()->insertRow(newRow);
640 QVariant
value = action->data();
642 if (
value.isValid()) {
643 QModelIndex index = currentIndex();
644 const int newRow = index.isValid() ? index.row() + 1 : 0;
646 m_d->model->insertOtherLayer(
value.toInt(), newRow);
652 QModelIndex index = currentIndex();
653 if (!index.isValid())
return;
654 model()->removeRow(index.row());
659 m_d->layerEditingMenu->exec(globalPos);
665 Q_FOREACH(
const QModelIndex &index, selectedIndices) {
666 if (!index.isValid() ||
668 selectedIndices.removeOne(index);
672 m_d->model->createFrame(selectedIndices);
677 QModelIndex index = currentIndex();
678 if (!index.isValid() ||
684 m_d->model->copyFrame(index);
691 if (!selectedIndices.isEmpty()) {
693 m_d->model->removeFramesAndOffset(selectedIndices);
695 m_d->model->removeFrames(selectedIndices);
704 if (!indexes.isEmpty()) {
705 m_d->model->mirrorFrames(indexes);
710 m_d->model->clearEntireCache();
715 const QModelIndex currentIndex =
716 !entireColumn ? this->currentIndex() :
m_d->model->index(0, this->currentIndex().column());
718 if (!currentIndex.isValid())
return;
720 QClipboard *cb = QApplication::clipboard();
721 const QMimeData *data = cb->mimeData();
723 if (data && data->hasFormat(
"application/x-krita-frame")) {
725 bool dataMoved =
false;
726 bool result =
m_d->model->dropMimeDataExtended(data, Qt::MoveAction, currentIndex, &dataMoved);
728 if (result && dataMoved) {
736 if (!
m_d->model)
return;
739 m_d->model->makeClonesUnique(indices);
744 if (!
m_d->model)
return;
746 QString defaultDir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
748 const QString currentFile =
m_d->model->audioChannelFileName();
749 QDir baseDir = QFileInfo(currentFile).absoluteDir();
750 if (baseDir.exists()) {
751 defaultDir = baseDir.absolutePath();
755 const QFileInfo info(result);
758 m_d->model->setAudioChannelFileName(info);
764 if (!
m_d->model)
return;
766 if (
value !=
m_d->model->isAudioMuted()) {
773 if (!
m_d->model)
return;
774 m_d->model->setAudioChannelFileName(QFileInfo());
779 m_d->model->setAudioVolume(qreal(
value) / 100.0);
784 if (state == QScroller::Dragging || state == QScroller::Scrolling ) {
785 m_d->kineticScrollInfiniteFrameUpdater.start(16);
787 m_d->kineticScrollInfiniteFrameUpdater.stop();
795 const int originalFirstColumn =
m_d->horizontalRuler->estimateFirstVisibleColumn();
796 if (
m_d->horizontalRuler->setZoom(
m_d->horizontalRuler->zoom() + zoom)) {
797 const int newLastColumn =
m_d->horizontalRuler->estimateFirstVisibleColumn();
798 if (newLastColumn >=
m_d->model->columnCount()) {
801 viewport()->update();
807 if(
m_d->dragInProgress ||
808 (
m_d->model->isScrubbing() && horizontalScrollBar()->sliderPosition() == horizontalScrollBar()->maximum()) ) {
814 QScrollBar* hBar = horizontalScrollBar();
815 QScrollBar* vBar = verticalScrollBar();
817 QSize desiredScrollArea = QSize(width() - verticalHeader()->width(), height() - horizontalHeader()->height());
820 if (hBar->isVisible() && vBar->isVisible()) {
821 desiredScrollArea -= QSize(vBar->width(), hBar->height());
824 hBar->parentWidget()->layout()->setAlignment(Qt::AlignRight);
825 hBar->setMaximumWidth(desiredScrollArea.width());
826 hBar->setMinimumWidth(desiredScrollArea.width());
828 vBar->parentWidget()->layout()->setAlignment(Qt::AlignBottom);
829 vBar->setMaximumHeight(desiredScrollArea.height());
830 vBar->setMinimumHeight(desiredScrollArea.height());
835 QModelIndex index = currentIndex();
836 if (!index.isValid() || row < 0)
return;
838 index =
m_d->model->index(row, index.column());
844 QTimer::singleShot(16, Qt::PreciseTimer,
this, [
this, index](){
851 QModelIndex startIndex = currentIndex().siblingAtColumn(start);
852 if (!startIndex.isValid() || start < 0)
return;
856 m_d->horizontalRuler->zoomToFitFrameRange(start, end);
862 QSet<int> activeLayerSelectedTimes;
863 Q_FOREACH (
const QModelIndex& index, selection) {
865 activeLayerSelectedTimes.insert(index.column());
869 m_d->model->setActiveLayerSelectedTimes(activeLayerSelectedTimes);
897 return QTableView::viewportEvent(event);
902 QPersistentModelIndex index = indexAt(event->pos());
904 if (
m_d->modifiersCatcher->modifierPressed(
"pan-zoom")) {
905 if (event->button() == Qt::RightButton) {
910 }
else if (event->button() == Qt::LeftButton) {
911 m_d->initialDragPanPos =
event->pos();
912 m_d->initialDragPanValue =
913 QPoint(horizontalScrollBar()->
value(),
914 verticalScrollBar()->
value());
918 }
else if (event->button() == Qt::RightButton) {
919 int numSelectedItems = selectionModel()->selectedIndexes().size();
921 if (index.isValid() &&
922 numSelectedItems <= 1 &&
928 setCurrentIndex(index);
936 int labelIndex = colorLabel.isValid() ? colorLabel.toInt() : 0;
937 m_d->colorSelector->colorLabelSelector()->setCurrentIndex(labelIndex);
945 menu.addAction(
m_d->colorSelectorAction);
946 menu.exec(event->globalPos());
952 m_d->colorSelector->colorLabelSelector()->setCurrentIndex(labelIndex);
958 menu.addAction(
m_d->colorSelectorAction);
959 menu.exec(event->globalPos());
961 }
else if (numSelectedItems > 1) {
963 bool firstKeyframe =
true;
964 bool hasKeyframes =
false;
965 bool containsClones =
false;
966 Q_FOREACH(QModelIndex index, selectedIndexes()) {
971 if (colorLabel.isValid()) {
973 labelIndex = colorLabel.toInt();
974 }
else if (labelIndex != colorLabel.toInt()) {
979 firstKeyframe =
false;
985 && labelIndex == -1) {
992 m_d->multiframeColorSelector->colorLabelSelector()->setCurrentIndex(labelIndex);
1000 menu.addAction(
m_d->multiframeColorSelectorAction);
1001 menu.exec(event->globalPos());
1004 }
else if (event->button() == Qt::MiddleButton) {
1005 QModelIndex index = model()->buddy(indexAt(event->pos()));
1006 if (index.isValid()) {
1007#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1008 QStyleOptionViewItem option = viewOptions();
1010 QStyleOptionViewItem option;
1011 initViewItemOption(&option);
1013 option.rect = visualRect(index);
1015 m_d->tip.showTip(
this, event->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index);
1020 if (index.isValid()) {
1021 m_d->model->setLastClickedIndex(index);
1024 m_d->lastPressedPosition = QPoint(horizontalOffset(), verticalOffset()) +
event->pos();
1025 m_d->lastPressedModifier =
event->modifiers();
1027 m_d->initialDragPanPos =
event->pos();
1029 QAbstractItemView::mousePressEvent(event);
1034 QPersistentModelIndex index = indexAt(event->pos());
1036 if (index.isValid()) {
1037 if (event->modifiers() & Qt::AltModifier) {
1038 selectRow(index.row());
1040 selectColumn(index.column());
1044 QAbstractItemView::mouseDoubleClickEvent(event);
1050 if (state() == DraggingState &&
1051 (horizontalHeader()->defaultSectionSize() / 2) < QApplication::startDragDistance() ) {
1053 const QPoint dragVector = e->pos() -
m_d->initialDragPanPos;
1054 if (dragVector.manhattanLength() >= (horizontalHeader()->defaultSectionSize() / 2)) {
1055 startDrag(model()->supportedDragActions());
1061 if (
m_d->modifiersCatcher->modifierPressed(
"pan-zoom")) {
1062 if (e->buttons() & Qt::RightButton) {
1064 }
else if (e->buttons() & Qt::LeftButton) {
1066 QPoint diff = e->pos() -
m_d->initialDragPanPos;
1067 QPoint offset = QPoint(
m_d->initialDragPanValue.x() - diff.x(),
1068 m_d->initialDragPanValue.y() - diff.y());
1070 const int height =
m_d->layersHeader->defaultSectionSize();
1072 if (
m_d->initialDragPanValue.x() - diff.x() > horizontalScrollBar()->maximum() ||
m_d->initialDragPanValue.x() - diff.x() > horizontalScrollBar()->minimum() ){
1077 horizontalScrollBar()->setValue(offset.x());
1078 verticalScrollBar()->setValue(offset.y() / height);
1082 }
else if (e->buttons() == Qt::MiddleButton) {
1083 QModelIndex index = model()->buddy(indexAt(e->pos()));
1084 if (index.isValid()) {
1085#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1086 QStyleOptionViewItem option = viewOptions();
1088 QStyleOptionViewItem option;
1089 initViewItemOption(&option);
1091 option.rect = visualRect(index);
1093 m_d->tip.showTip(
this, e->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index);
1098 m_d->model->setScrubState(
true);
1099 QTableView::mouseMoveEvent(e);
1105 if (
m_d->modifiersCatcher->modifierPressed(
"pan-zoom")) {
1108 m_d->model->setScrubState(
false);
1109 QTableView::mouseReleaseEvent(e);
1117 if (!indexes.isEmpty() &&
m_d->modifiersCatcher->modifierPressed(
"offset-frame")) {
1119 int leftmostColumn = std::numeric_limits<int>::max();
1121 Q_FOREACH (
const QModelIndex &index, indexes) {
1122 leftmostColumn = qMin(leftmostColumn, index.column());
1123 if (!rows.contains(index.row())) {
1124 rows.append(index.row());
1128 const int lastColumn =
m_d->model->columnCount() - 1;
1130 selectionModel()->clear();
1131 Q_FOREACH (
const int row, rows) {
1132 QItemSelection sel(
m_d->model->index(row, leftmostColumn),
m_d->model->index(row, lastColumn));
1133 selectionModel()->select(sel, QItemSelectionModel::Select);
1136 supportedActions = Qt::MoveAction;
1140 for(
int i = indexes.count() - 1 ; i >= 0; --i) {
1142 indexes.removeAt(i);
1145 selectionModel()->clear();
1147 if (indexes.count() > 0) {
1148 QMimeData *data =
m_d->model->mimeData(indexes);
1152 QPixmap pixmap =
m_d->renderToPixmap(indexes, &
rect);
1153 rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
1154 QDrag *drag =
new QDrag(
this);
1155 drag->setPixmap(pixmap);
1156 drag->setMimeData(data);
1157 drag->setHotSpot(
m_d->lastPressedPosition -
rect.topLeft());
1158 drag->exec(supportedActions, Qt::MoveAction);
1159 setCurrentIndex(currentIndex());
1172 if (
m_d->lastPressedModifier & Qt::ShiftModifier) {
1199 QModelIndexList selectionBefore = selectionModel()->selectedIndexes();
1200 QModelIndex currentBefore = selectionModel()->currentIndex();
1203 m_d->dragWasSuccessful =
false;
1204 QAbstractItemView::startDrag(supportedActions);
1206 QModelIndex newCurrent;
1207 QPoint selectionOffset;
1209 if (
m_d->dragWasSuccessful) {
1210 newCurrent = currentIndex();
1211 selectionOffset = QPoint(newCurrent.column() - currentBefore.column(),
1212 newCurrent.row() - currentBefore.row());
1214 newCurrent = currentBefore;
1215 selectionOffset = QPoint();
1218 setCurrentIndex(newCurrent);
1219 selectionModel()->clearSelection();
1220 Q_FOREACH (
const QModelIndex &idx, selectionBefore) {
1221 QModelIndex newIndex =
1222 model()->index(idx.row() + selectionOffset.y(),
1223 idx.column() + selectionOffset.x());
1224 selectionModel()->select(newIndex, QItemSelectionModel::Select);
1231 m_d->dragInProgress =
true;
1232 m_d->model->setScrubState(
true);
1234 QTableView::dragEnterEvent(event);
1239 m_d->dragInProgress =
true;
1240 m_d->model->setScrubState(
true);
1242 QAbstractItemView::dragMoveEvent(event);
1247 if (!event->isAccepted() && selectionModel()->isSelected(indexAt(event->pos()))) {
1248 event->setAccepted(
true);
1251 if (event->isAccepted()) {
1252 QModelIndex index = indexAt(event->pos());
1254 if (!
m_d->model->canDropFrameData(event->mimeData(), index)) {
1257 selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
1264 m_d->dragInProgress =
false;
1265 m_d->model->setScrubState(
false);
1267 QAbstractItemView::dragLeaveEvent(event);
1272 m_d->dragInProgress =
false;
1273 m_d->model->setScrubState(
false);
1275 if (event->keyboardModifiers() & Qt::ControlModifier) {
1276 event->setDropAction(Qt::CopyAction);
1277 }
else if (event->keyboardModifiers() & Qt::AltModifier) {
1278 event->setDropAction(Qt::LinkAction);
1281 QAbstractItemView::dropEvent(event);
1284 QModelIndex index = indexAt(event->pos());
1285 if (!event->isAccepted() && selectionModel()->isSelected(index)) {
1286 event->setAccepted(
true);
1287 const Qt::DropAction action =
event->dropAction();
1288 const int row =
event->pos().y();
1289 const int column =
event->pos().x();
1290 if (
m_d->model->dropMimeData(event->mimeData(), action, row, column, index)) {
1291 event->acceptProposedAction();
1295 m_d->dragWasSuccessful =
event->isAccepted();
1300 const int scrollDirection = e->angleDelta().y() > 0 ? 1 : -1;
1301 bool mouseOverLayerPanel = verticalHeader()->geometry().contains(verticalHeader()->mapFromGlobal(e->globalPosition().toPoint()));
1303 if (mouseOverLayerPanel) {
1304 QTableView::wheelEvent(e);
1306 QModelIndex index = currentIndex();
1309 if (index.isValid()) {
1310 column = index.column() + scrollDirection;
1313 if (column >= 0 && !
m_d->dragInProgress) {
1315 setCurrentIndex(
m_d->model->index(index.row(), column));
1330 QTableView::rowsInserted(parent, start, end);
1335 QTableView::currentChanged(current, previous);
1337 if (previous.column() != current.column()) {
1340 if ( current.column() !=
m_d->model->currentTime() ) {
1347 const QEvent *event)
const
1362 (event->type() == QEvent::MouseButtonPress ||
1363 event->type() == QEvent::MouseButtonRelease) &&
1366 const QMouseEvent *mevent =
static_cast<const QMouseEvent*
>(event);
1368 if (mevent->button() == Qt::RightButton &&
1369 selectionModel()->selectedIndexes().contains(index)) {
1372 return QItemSelectionModel::NoUpdate;
1375 if (event->type() == QEvent::MouseButtonPress &&
1376 (mevent->modifiers() & Qt::ControlModifier)) {
1378 return QItemSelectionModel::NoUpdate;
1381 if (event->type() == QEvent::MouseButtonRelease &&
1382 (mevent->modifiers() & Qt::ControlModifier)) {
1384 return QItemSelectionModel::Toggle;
1388 return QAbstractItemView::selectionCommand(index, event);
1394 m_d->horizontalRuler->setFramePerSecond(fps);
1409 for (
int i = 0; i <
m_d->model->rowCount(); i++) {
1413 for (
int column = minColumn; column <= maxColumn; column++) {
1414 indexes <<
m_d->model->index(i, column);
1418 Q_FOREACH (
const QModelIndex &index, selectionModel()->selectedIndexes()) {
1430 minColumn = std::numeric_limits<int>::max();
1431 maxColumn = std::numeric_limits<int>::min();
1433 Q_FOREACH (
const QModelIndex &index, selectionModel()->selectedIndexes()) {
1434 if (!ignoreEditability &&
1437 rows.insert(index.row());
1438 minColumn = qMin(minColumn, index.column());
1439 maxColumn = qMax(maxColumn, index.column());
1446 int minColumn = 0, maxColumn = 0;
1449 if (minColumn > maxColumn)
return;
1452 count = qMax(1, maxColumn - minColumn + 1);
1455 const int insertionColumn =
1457 maxColumn + 1 : minColumn;
1461 for (
int i = 0; i <
m_d->model->rowCount(); i++) {
1467 if (!rows.isEmpty()) {
1468 m_d->model->insertFrames(insertionColumn,
QList<int>(rows.begin(), rows.end()), count, timing);
1477 if (
m_d->insertKeyframeDialog->promptUserSettings(count, timing, direction)) {
1487 if (!entireColumn) {
1488 Q_FOREACH (
const QModelIndex &index, selectionModel()->selectedIndexes()) {
1494 const int column = selectionModel()->currentIndex().column();
1496 for (
int i = 0; i <
m_d->model->rowCount(); i++) {
1497 const QModelIndex index =
m_d->model->index(i, column);
1504 if (!indexes.isEmpty()) {
1505 m_d->model->insertHoldFrames(indexes, count);
1520 const int count = QInputDialog::getInt(
this,
1521 i18nc(
"@title:window",
"Insert or Remove Hold Frames"),
1522 i18nc(
"@label:spinbox",
"Enter number of frames"),
1524 m_d->insertKeyframeDialog->defaultTimingOfAddedFrames() :
1525 m_d->insertKeyframeDialog->defaultNumberOfHoldFramesToRemove(),
1530 m_d->insertKeyframeDialog->setDefaultTimingOfAddedFrames(count);
1533 m_d->insertKeyframeDialog->setDefaultNumberOfHoldFramesToRemove(count);
1541 QMap<int, QList<int>> indexMap;
1545 foreach (
const QModelIndex &index, selectedIndices) {
1546 if (!indexMap.contains(index.row())) {
1551 indexMap[index.row()] << index.column();
1556 selectionModel()->clearSelection();
1557 foreach (
const int &layer, indexMap.keys()) {
1559 int progressIndex = 0;
1561 std::sort(indexMap[layer].begin(), indexMap[layer].end());
1562 for (it = indexMap[layer].constBegin(); it != indexMap[layer].constEnd(); it++) {
1563 const int offsetColumn = *it + (progressIndex * count);
1564 selectionModel()->select(model()->index(layer, offsetColumn), QItemSelectionModel::Select);
1573 if (selectedIndices.isEmpty())
return;
1575 int minColumn = std::numeric_limits<int>::max();
1576 int minRow = std::numeric_limits<int>::max();
1577 Q_FOREACH (
const QModelIndex &index, selectedIndices) {
1578 minRow = qMin(minRow, index.row());
1579 minColumn = qMin(minColumn, index.column());
1582 const QModelIndex baseIndex =
m_d->model->index(minRow, minColumn);
1583 QMimeData *data =
m_d->model->mimeDataExtended(selectedIndices,
1590 QClipboard *cb = QApplication::clipboard();
1591 cb->setMimeData(data);
1598 if (selectedIndices.isEmpty())
return;
1600 int minColumn = std::numeric_limits<int>::max();
1601 int minRow = std::numeric_limits<int>::max();
1602 Q_FOREACH (
const QModelIndex &index, selectedIndices) {
1603 minRow = qMin(minRow, index.row());
1604 minColumn = qMin(minColumn, index.column());
1607 const QModelIndex baseIndex =
m_d->model->index(minRow, minColumn);
1608 QMimeData *data =
m_d->model->mimeDataExtended(selectedIndices,
1613 QClipboard *cb = QApplication::clipboard();
1614 cb->setMimeData(data);
1627 bool selectionExists = minColumn != maxColumn;
1629 menu->addSection(i18n(
"Edit Frames:"));
1630 menu->addSeparator();
1632 if (selectionExists) {
1639 menu->addSeparator();
1649 if (!emptyFrame && cloneFrameSelected) {
1653 menu->addSeparator();
1656 QMenu *frames = menu->addMenu(i18nc(
"@item:inmenu",
"Keyframes"));
1659 frames->addSeparator();
1664 QMenu *hold = menu->addMenu(i18nc(
"@item:inmenu",
"Hold Frames"));
1667 hold->addSeparator();
1672 menu->addSeparator();
1679 menu->addSeparator();
1684 menu->addSeparator();
1689 const int sectionWidth =
m_d->horizontalRuler->defaultSectionSize();
1690 return sectionWidth * column;
1695#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1696 QStyleOptionViewItem option =
q->viewOptions();
1698 QStyleOptionViewItem option;
1699 q->initViewItemOption(&option);
1701 option.locale =
q->locale();
1702 option.locale.setNumberOptions(QLocale::OmitGroupSeparator);
1711 const QRect viewportRect = q->viewport()->rect();
1713 for (
int i = 0; i < indexes.count(); ++i) {
1714 const QModelIndex &index = indexes.at(i);
1715 const QRect current = q->visualRect(index);
1716 if (current.intersects(viewportRect)) {
1717 ret += qMakePair(current, index);
1721 rect &= viewportRect;
1730 if (paintPairs.isEmpty())
1733 QPixmap pixmap(r->size());
1734 pixmap.fill(Qt::transparent);
1736 QPainter painter(&pixmap);
1738 QStyleOptionViewItem option = viewOptionsV4();
1739 option.state |= QStyle::State_Selected;
1741 for (
int j = 0; j < paintPairs.count(); ++j) {
1742 option.rect = paintPairs.at(j).first.translated(-r->topLeft());
1743 const QModelIndex ¤t = paintPairs.at(j).second;
1746 q->itemDelegate(current)->paint(&painter, option, current);
1757 buttonSize = QSize(minimalSize, minimalSize);
1765 return (model->flags(index) & Qt::ItemIsDragEnabled);
float value(const T *src, size_t ch)
bool isIndexDragEnabled(QAbstractItemModel *model, const QModelIndex &index)
void resizeToMinimalSize(QAbstractButton *w, int minimalSize)
QPair< QRect, QModelIndex > QItemViewPaintPair
QList< QItemViewPaintPair > QItemViewPaintPairs
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
A KisActionManager class keeps track of KisActions. These actions are always associated with the GUI....
static void safePopulateMenu(QMenu *menu, const QString &actionId, KisActionManager *actionManager)
@ FrameColorLabelIndexRole
void setFramesPerSecond(int fps)
void slotUpdateDragInfiniteFramesCount()
void slotAudioChannelMute(bool value)
void insertOrRemoveMultipleHoldFrames(bool insertion, bool entireColumn=false)
void slotInsertMultipleHoldFrameColumns()
~KisAnimTimelineFramesView() override
void slotRemoveSelectedFrames(bool entireColumn=false, bool pull=false)
void slotReselectCurrentIndex()
void slotEnsureRowVisible(int row)
void slotRemoveMultipleHoldFrameColumns()
void slotZoom(qreal zoom)
void slotInsertKeyframeRight()
void slotFitViewToFrameRange(int start, int end)
void slotCanvasUpdate(class KoCanvasBase *canvas)
void mouseReleaseEvent(QMouseEvent *e) override
void insertOrRemoveHoldFrames(int count, bool entireColumn=false)
void dragEnterEvent(QDragEnterEvent *event) override
void slotUpdateLayersMenu()
void slotSelectionChanged()
void slotUpdateFrameActions()
void wheelEvent(QWheelEvent *e) override
void setActionManager(KisActionManager *actionManager)
void slotRemoveSelectedColumns()
void slotInsertKeyframeColumnLeft()
void slotInsertMultipleHoldFrames()
void dropEvent(QDropEvent *event) override
void slotInsertMultipleKeyframeColumns()
void slotColorLabelChanged(int)
void insertKeyframes(int count=1, int timing=1, TimelineDirection direction=TimelineDirection::LEFT, bool entireColumn=false)
void slotSetEndTimeToCurrentPosition()
void slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
void slotAddExistingLayer(QAction *action)
void slotSelectAudioChannelFile()
void slotInsertKeyframeColumnRight()
void dragLeaveEvent(QDragLeaveEvent *event) override
void calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet< int > &rows, bool ignoreEditability) const
void startDrag(Qt::DropActions supportedActions) override
void slotHeaderDataChanged(Qt::Orientation orientation, int first, int last)
void slotAudioChannelRemove()
void slotUpdateInfiniteFramesCount()
void slotInsertMultipleKeyframes()
void dragMoveEvent(QDragMoveEvent *event) override
void slotMirrorFrames(bool entireColumn=false)
void slotRemoveSelectedFramesAndShift()
void slotRemoveHoldFrame()
void slotMakeClonesUnique()
void mouseMoveEvent(QMouseEvent *e) override
void clone(bool entireColumn)
bool viewportEvent(QEvent *event) override
void updateGeometries() override
void slotAudioVolumeChanged(int value)
int scrollPositionFromColumn(int column)
void rowsInserted(const QModelIndex &parent, int start, int end) override
void slotRemoveMultipleHoldFrames()
const QScopedPointer< Private > m_d
void slotRemoveHoldFrameColumn()
void resizeEvent(QResizeEvent *e) override
void slotAddDuplicateFrame()
void slotSetStartTimeToCurrentPosition()
void slotInsertHoldFrameColumn()
void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override
void slotRemoveSelectedColumnsAndShift()
void mousePressEvent(QMouseEvent *event) override
void createFrameEditingMenuActions(QMenu *menu, bool emptyFrame, bool cloneFrameSelected)
void slotInsertHoldFrame()
void cutCopyImpl(bool entireColumn, bool copy)
void slotScrollerStateChanged(QScroller::State state)
void slotTryTransferSelectionBetweenRows(int fromRow, int toRow)
KisAnimTimelineFramesView(QWidget *parent)
void slotRealignScrollBars()
QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index, const QEvent *event) const override
void fanSelectedFrames(const QModelIndexList &selection, int count, bool ignoreKeyless=true)
void slotLayerContextMenuRequested(const QPoint &globalPos)
void slotUpdatePlaybackRange()
void mouseDoubleClickEvent(QMouseEvent *event) override
void setModel(QAbstractItemModel *model) override
void insertMultipleKeyframes(bool entireColumn=false)
void slotPasteFrames(bool entireColumn=false)
QModelIndexList calculateSelectionSpan(bool entireColumn, bool editableOnly=true) const
void slotInsertKeyframeLeft()
void calculateActiveLayerSelectedTimes(const QModelIndexList &selection)
KisCanvasAnimationState * animationState() const
The KisCanvasAnimationState class stores all of the canvas-specific animation state.
The KisCustomModifiersCatcher class is a special utility class that tracks custom modifiers pressed....
int defaultFrameColorLabel() const
void setDefaultFrameColorLabel(int label)
static QString askForAudioFileName(const QString &defaultDir, QWidget *parent)
static KisTimeSpan fromTimeWithDuration(int start, int duration)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
const QString newLayerActionName
const QString removeLayerActionName
const QString pinExistingLayerActionName
QIcon loadIcon(const QString &name)
QWidgetAction * colorSelectorAction
KisAnimTimelineTimeHeader * horizontalRuler
QTimer kineticScrollInfiniteFrameUpdater
QPoint lastPressedPosition
QPoint initialDragPanValue
KisSignalCompressor selectionChangedCompressor
QToolButton * addLayersButton
Qt::KeyboardModifiers lastPressedModifier
KisAction * pinLayerToTimelineAction
TimelineInsertKeyframeDialog * insertKeyframeDialog
KisCustomModifiersCatcher * modifiersCatcher
QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r) const
KisColorLabelSelectorWidgetMenuWrapper * multiframeColorSelector
QMenu * existingLayersMenu
QWidgetAction * multiframeColorSelectorAction
KisAnimTimelineFramesModel * model
KisActionManager * actionMan
KisAnimTimelineLayersHeader * layersHeader
KisAnimTimelineFramesView * q
KisZoomButton * zoomDragButton
Private(KisAnimTimelineFramesView *_q)
QStyleOptionViewItem viewOptionsV4() const
QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const
KisColorLabelSelectorWidgetMenuWrapper * colorSelector