17#include <QApplication>
23#include <QKeySequence>
24#include <QInputDialog>
30#include "KSharedConfig"
134 : QTableView(parent),
138 m_d->modifiersCatcher->addModifier(
"pan-zoom", Qt::Key_Space);
139 m_d->modifiersCatcher->addModifier(
"offset-frame", Qt::Key_Shift);
141 setCornerButtonEnabled(
false);
142 setSelectionBehavior(QAbstractItemView::SelectItems);
143 setSelectionMode(QAbstractItemView::ExtendedSelection);
147 setDragEnabled(
true);
148 setDragDropMode(QAbstractItemView::DragDrop);
149 setAcceptDrops(
true);
150 setDropIndicatorShown(
true);
151 setDefaultDropAction(Qt::MoveAction);
154 this->setHorizontalHeader(
m_d->horizontalRuler);
179 m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed);
181 m_d->layersHeader->setDefaultSectionSize(24);
182 m_d->layersHeader->setMinimumWidth(60);
183 m_d->layersHeader->setHighlightSections(
true);
184 this->setVerticalHeader(
m_d->layersHeader);
188 m_d->layerEditingMenu =
new QMenu(
this);
189 m_d->layerEditingMenu->addSection(i18n(
"Edit Layers:"));
190 m_d->layerEditingMenu->addSeparator();
194 m_d->layerEditingMenu->addSeparator();
202 m_d->addLayersButton =
new QToolButton(
this);
203 m_d->addLayersButton->setAutoRaise(
true);
205 m_d->addLayersButton->setIconSize(QSize(22, 22));
206 m_d->addLayersButton->setPopupMode(QToolButton::InstantPopup);
207 m_d->addLayersButton->setMenu(
m_d->layerEditingMenu);
213 m_d->colorSelector->installEventFilter(clickIgnore);
214 m_d->colorSelectorAction =
new QWidgetAction(
this);
215 m_d->colorSelectorAction->setDefaultWidget(
m_d->colorSelector);
219 m_d->multiframeColorSelector->installEventFilter(clickIgnore);
220 m_d->multiframeColorSelectorAction =
new QWidgetAction(
this);
221 m_d->multiframeColorSelectorAction->setDefaultWidget(
m_d->multiframeColorSelector);
231 m_d->zoomDragButton->setAutoRaise(
true);
233 m_d->zoomDragButton->setIconSize(QSize(22, 22));
235 m_d->zoomDragButton->setToolTip(i18nc(
"@info:tooltip",
"Zoom Timeline. Hold down and drag left or right."));
236 m_d->zoomDragButton->setPopupMode(QToolButton::InstantPopup);
237 connect(
m_d->zoomDragButton, SIGNAL(zoom(qreal)), SLOT(
slotZoom(qreal)));
242 setHorizontalScrollBar(hZoomableBar);
243 setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
244 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
246 hZoomableBar->setEnabled(
false);
249 connect(hZoomableBar, SIGNAL(zoom(qreal)),
this, SLOT(
slotZoom(qreal)));
262 connect(scroller, SIGNAL(stateChanged(QScroller::State)),
265 connect(&
m_d->kineticScrollInfiniteFrameUpdater, &QTimer::timeout, [
this, scroller](){
266 slotUpdateInfiniteFramesCount();
267 scroller->resendPrepareEvent();
270 QScrollerProperties props = scroller->scrollerProperties();
271 props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
272 props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
273 scroller->setScrollerProperties(props);
277 connect(&
m_d->selectionChangedCompressor, SIGNAL(timeout()),
279 connect(&
m_d->selectionChangedCompressor, SIGNAL(timeout()),
283 QClipboard *cb = QApplication::clipboard();
297 m_d->model = framesModel;
299 QTableView::setModel(model);
301 connect(
m_d->model, SIGNAL(headerDataChanged(Qt::Orientation,
int,
int)),
304 connect(
m_d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
307 connect(
m_d->model, SIGNAL(rowsRemoved(QModelIndex,
int,
int)),
310 connect(
m_d->model, SIGNAL(sigInfiniteTimelineUpdateNeeded()),
313 connect(
m_d->model, SIGNAL(requestTransferSelectionBetweenRows(
int,
int)),
316 connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
317 &
m_d->selectionChangedCompressor, SLOT(start()));
324 m_d->actionMan = actionManager;
325 m_d->horizontalRuler->setActionManager(actionManager);
330 action =
m_d->actionMan->createAction(
"add_blank_frame");
333 action =
m_d->actionMan->createAction(
"add_duplicate_frame");
336 action =
m_d->actionMan->createAction(
"insert_keyframe_left");
339 action =
m_d->actionMan->createAction(
"insert_keyframe_right");
342 action =
m_d->actionMan->createAction(
"insert_multiple_keyframes");
345 action =
m_d->actionMan->createAction(
"remove_frames_and_pull");
348 action =
m_d->actionMan->createAction(
"remove_frames");
351 action =
m_d->actionMan->createAction(
"insert_hold_frame");
354 action =
m_d->actionMan->createAction(
"insert_multiple_hold_frames");
357 action =
m_d->actionMan->createAction(
"remove_hold_frame");
360 action =
m_d->actionMan->createAction(
"remove_multiple_hold_frames");
363 action =
m_d->actionMan->createAction(
"mirror_frames");
366 action =
m_d->actionMan->createAction(
"copy_frames");
369 action =
m_d->actionMan->createAction(
"copy_frames_as_clones");
370 connect(action, &KisAction::triggered, [
this](){
clone(
false);});
372 action =
m_d->actionMan->createAction(
"make_clones_unique");
375 action =
m_d->actionMan->createAction(
"cut_frames");
378 action =
m_d->actionMan->createAction(
"paste_frames");
381 action =
m_d->actionMan->createAction(
"set_start_time");
384 action =
m_d->actionMan->createAction(
"set_end_time");
387 action =
m_d->actionMan->createAction(
"update_playback_range");
390 action =
m_d->actionMan->actionByName(
"pin_to_timeline");
391 m_d->pinLayerToTimelineAction = action;
392 m_d->layerEditingMenu->addAction(action);
398 QTableView::updateGeometries();
400 const int availableHeight =
m_d->horizontalRuler->height();
401 const int margin = 2;
402 const int minimalSize = availableHeight - 2 * margin;
408 int y = (availableHeight - minimalSize) / 2;
409 m_d->addLayersButton->move(x, 2 * y);
411 const int availableWidth =
m_d->layersHeader->width();
413 x = availableWidth - margin - minimalSize;
414 m_d->zoomDragButton->move(x, 2 * y);
423 state->disconnect(
this);
427 m_d->canvas = canvas;
429 horizontalScrollBar()->setEnabled(
m_d->canvas !=
nullptr);
442 m_d->existingLayersMenu->clear();
445 if (
value.isValid()) {
450 action =
m_d->existingLayersMenu->addAction(l.
name);
451 action->setData(i++);
458 if (!
m_d->actionMan)
return;
461 const bool hasEditableFrames = !editableIndexes.isEmpty();
463 bool hasExistingFrames =
false;
464 Q_FOREACH (
const QModelIndex &index, editableIndexes) {
466 hasExistingFrames =
true;
471 auto enableAction = [
this] (
const QString &id,
bool value) {
474 action->setEnabled(
value);
477 enableAction(
"add_blank_frame", hasEditableFrames);
478 enableAction(
"add_duplicate_frame", hasEditableFrames);
480 enableAction(
"insert_keyframe_left", hasEditableFrames);
481 enableAction(
"insert_keyframe_right", hasEditableFrames);
482 enableAction(
"insert_multiple_keyframes", hasEditableFrames);
484 enableAction(
"remove_frames", hasEditableFrames && hasExistingFrames);
485 enableAction(
"remove_frames_and_pull", hasEditableFrames);
487 enableAction(
"insert_hold_frame", hasEditableFrames);
488 enableAction(
"insert_multiple_hold_frames", hasEditableFrames);
490 enableAction(
"remove_hold_frame", hasEditableFrames);
491 enableAction(
"remove_multiple_hold_frames", hasEditableFrames);
493 enableAction(
"mirror_frames", hasEditableFrames && editableIndexes.size() > 1);
495 enableAction(
"copy_frames",
true);
496 enableAction(
"cut_frames", hasEditableFrames);
501 int minColumn = std::numeric_limits<int>::max();
502 int maxColumn = std::numeric_limits<int>::min();
506 foreach (
const QModelIndex &idx, selectedIndexes()) {
507 if (idx.column() > maxColumn) {
508 maxColumn = idx.column();
511 if (idx.column() < minColumn) {
512 minColumn = idx.column();
517 if (maxColumn > minColumn) {
521 m_d->model->setPlaybackRange(range);
526 QModelIndex index = currentIndex();
533 QModelIndex current = model()->index(toRow,
m_d->model->currentTime());
534 if (selectedIndexes().count() <= 1) {
535 if (selectedIndexes().count() != 1 ||
536 (selectedIndexes().first().column() == current.column() &&
537 selectedIndexes().first().row() == fromRow)) {
538 setCurrentIndex(current);
546 m_d->model->setDocumentClipRangeStart(this->currentIndex().column());
551 m_d->model->setDocumentClipRangeEnd(this->currentIndex().column());
562 m_d->model->setDocumentClipRangeStart(minColumn);
563 m_d->model->setDocumentClipRangeEnd(maxColumn);
568 const int lastVisibleFrame =
m_d->horizontalRuler->estimateLastVisibleColumn();
569 m_d->model->setLastVisibleFrame(lastVisibleFrame);
574 if (
m_d->model->isPlaybackActive())
return;
576 int selectedColumn = -1;
578 for (
int j = topLeft.column(); j <= bottomRight.column(); j++) {
580 m_d->model->index(topLeft.row(), j),
589 QModelIndex index = currentIndex();
591 if (!index.isValid() && selectedColumn < 0) {
595 if (selectionModel()->selectedIndexes().count() > 1)
return;
597 if (selectedColumn == -1) {
598 selectedColumn = index.column();
601 if (selectedColumn != index.column() && !
m_d->dragInProgress && !
m_d->model->isScrubbing()) {
602 int row = index.isValid() ? index.row() : 0;
604 selectionModel()->setCurrentIndex(
m_d->model->index(row, selectedColumn), QItemSelectionModel::ClearAndSelect);
613 if (orientation == Qt::Horizontal) {
616 if (newFps !=
m_d->fps) {
626 Q_FOREACH(QModelIndex index, selectedIndexes()) {
634 QModelIndex index = currentIndex();
635 const int newRow = index.isValid() ? index.row() : 0;
636 model()->insertRow(newRow);
641 QVariant
value = action->data();
643 if (
value.isValid()) {
644 QModelIndex index = currentIndex();
645 const int newRow = index.isValid() ? index.row() + 1 : 0;
647 m_d->model->insertOtherLayer(
value.toInt(), newRow);
653 QModelIndex index = currentIndex();
654 if (!index.isValid())
return;
655 model()->removeRow(index.row());
660 m_d->layerEditingMenu->exec(globalPos);
666 Q_FOREACH(
const QModelIndex &index, selectedIndices) {
667 if (!index.isValid() ||
669 selectedIndices.removeOne(index);
673 m_d->model->createFrame(selectedIndices);
678 QModelIndex index = currentIndex();
679 if (!index.isValid() ||
685 m_d->model->copyFrame(index);
692 if (!selectedIndices.isEmpty()) {
694 m_d->model->removeFramesAndOffset(selectedIndices);
696 m_d->model->removeFrames(selectedIndices);
705 if (!indexes.isEmpty()) {
706 m_d->model->mirrorFrames(indexes);
711 m_d->model->clearEntireCache();
716 const QModelIndex currentIndex =
717 !entireColumn ? this->currentIndex() :
m_d->model->index(0, this->currentIndex().column());
719 if (!currentIndex.isValid())
return;
721 QClipboard *cb = QApplication::clipboard();
722 const QMimeData *data = cb->mimeData();
724 if (data && data->hasFormat(
"application/x-krita-frame")) {
726 bool dataMoved =
false;
727 bool result =
m_d->model->dropMimeDataExtended(data, Qt::MoveAction, currentIndex, &dataMoved);
729 if (result && dataMoved) {
737 if (!
m_d->model)
return;
740 m_d->model->makeClonesUnique(indices);
745 if (!
m_d->model)
return;
747 QString defaultDir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
749 const QString currentFile =
m_d->model->audioChannelFileName();
750 QDir baseDir = QFileInfo(currentFile).absoluteDir();
751 if (baseDir.exists()) {
752 defaultDir = baseDir.absolutePath();
756 const QFileInfo info(result);
759 m_d->model->setAudioChannelFileName(info);
765 if (!
m_d->model)
return;
767 if (
value !=
m_d->model->isAudioMuted()) {
774 if (!
m_d->model)
return;
775 m_d->model->setAudioChannelFileName(QFileInfo());
780 m_d->model->setAudioVolume(qreal(
value) / 100.0);
785 if (state == QScroller::Dragging || state == QScroller::Scrolling ) {
786 m_d->kineticScrollInfiniteFrameUpdater.start(16);
788 m_d->kineticScrollInfiniteFrameUpdater.stop();
796 const int originalFirstColumn =
m_d->horizontalRuler->estimateFirstVisibleColumn();
797 if (
m_d->horizontalRuler->setZoom(
m_d->horizontalRuler->zoom() + zoom)) {
798 const int newLastColumn =
m_d->horizontalRuler->estimateFirstVisibleColumn();
799 if (newLastColumn >=
m_d->model->columnCount()) {
802 viewport()->update();
808 if(
m_d->dragInProgress ||
809 (
m_d->model->isScrubbing() && horizontalScrollBar()->sliderPosition() == horizontalScrollBar()->maximum()) ) {
815 QScrollBar* hBar = horizontalScrollBar();
816 QScrollBar* vBar = verticalScrollBar();
818 QSize desiredScrollArea = QSize(width() - verticalHeader()->width(), height() - horizontalHeader()->height());
821 if (hBar->isVisible() && vBar->isVisible()) {
822 desiredScrollArea -= QSize(vBar->width(), hBar->height());
825 hBar->parentWidget()->layout()->setAlignment(Qt::AlignRight);
826 hBar->setMaximumWidth(desiredScrollArea.width());
827 hBar->setMinimumWidth(desiredScrollArea.width());
829 vBar->parentWidget()->layout()->setAlignment(Qt::AlignBottom);
830 vBar->setMaximumHeight(desiredScrollArea.height());
831 vBar->setMinimumHeight(desiredScrollArea.height());
836 QModelIndex index = currentIndex();
837 if (!index.isValid() || row < 0)
return;
839 index =
m_d->model->index(row, index.column());
845 QTimer::singleShot(16, Qt::PreciseTimer,
this, [
this, index](){
852 QModelIndex startIndex = currentIndex().siblingAtColumn(start);
853 if (!startIndex.isValid() || start < 0)
return;
857 m_d->horizontalRuler->zoomToFitFrameRange(start, end);
863 QSet<int> activeLayerSelectedTimes;
864 Q_FOREACH (
const QModelIndex& index, selection) {
866 activeLayerSelectedTimes.insert(index.column());
870 m_d->model->setActiveLayerSelectedTimes(activeLayerSelectedTimes);
898 return QTableView::viewportEvent(event);
903 QPersistentModelIndex index = indexAt(event->pos());
905 if (
m_d->modifiersCatcher->modifierPressed(
"pan-zoom")) {
906 if (event->button() == Qt::RightButton) {
911 }
else if (event->button() == Qt::LeftButton) {
912 m_d->initialDragPanPos =
event->pos();
913 m_d->initialDragPanValue =
914 QPoint(horizontalScrollBar()->
value(),
915 verticalScrollBar()->
value());
919 }
else if (event->button() == Qt::RightButton) {
920 int numSelectedItems = selectionModel()->selectedIndexes().size();
922 if (index.isValid() &&
923 numSelectedItems <= 1 &&
929 setCurrentIndex(index);
937 int labelIndex = colorLabel.isValid() ? colorLabel.toInt() : 0;
938 m_d->colorSelector->colorLabelSelector()->setCurrentIndex(labelIndex);
946 menu.addAction(
m_d->colorSelectorAction);
947 menu.exec(event->globalPos());
953 m_d->colorSelector->colorLabelSelector()->setCurrentIndex(labelIndex);
959 menu.addAction(
m_d->colorSelectorAction);
960 menu.exec(event->globalPos());
962 }
else if (numSelectedItems > 1) {
964 bool firstKeyframe =
true;
965 bool hasKeyframes =
false;
966 bool containsClones =
false;
967 Q_FOREACH(QModelIndex index, selectedIndexes()) {
972 if (colorLabel.isValid()) {
974 labelIndex = colorLabel.toInt();
975 }
else if (labelIndex != colorLabel.toInt()) {
980 firstKeyframe =
false;
986 && labelIndex == -1) {
993 m_d->multiframeColorSelector->colorLabelSelector()->setCurrentIndex(labelIndex);
1000 menu.addSeparator();
1001 menu.addAction(
m_d->multiframeColorSelectorAction);
1002 menu.exec(event->globalPos());
1005 }
else if (event->button() == Qt::MiddleButton) {
1006 QModelIndex index = model()->buddy(indexAt(event->pos()));
1007 if (index.isValid()) {
1008#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1009 QStyleOptionViewItem option = viewOptions();
1011 QStyleOptionViewItem option;
1012 initViewItemOption(&option);
1014 option.rect = visualRect(index);
1016 m_d->tip.showTip(event->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index);
1021 if (index.isValid()) {
1022 m_d->model->setLastClickedIndex(index);
1025 m_d->lastPressedPosition = QPoint(horizontalOffset(), verticalOffset()) +
event->pos();
1026 m_d->lastPressedModifier =
event->modifiers();
1028 m_d->initialDragPanPos =
event->pos();
1030 QAbstractItemView::mousePressEvent(event);
1035 QPersistentModelIndex index = indexAt(event->pos());
1037 if (index.isValid()) {
1038 if (event->modifiers() & Qt::AltModifier) {
1039 selectRow(index.row());
1041 selectColumn(index.column());
1045 QAbstractItemView::mouseDoubleClickEvent(event);
1051 if (state() == DraggingState &&
1052 (horizontalHeader()->defaultSectionSize() / 2) < QApplication::startDragDistance() ) {
1054 const QPoint dragVector = e->pos() -
m_d->initialDragPanPos;
1055 if (dragVector.manhattanLength() >= (horizontalHeader()->defaultSectionSize() / 2)) {
1056 startDrag(model()->supportedDragActions());
1062 if (
m_d->modifiersCatcher->modifierPressed(
"pan-zoom")) {
1063 if (e->buttons() & Qt::RightButton) {
1065 }
else if (e->buttons() & Qt::LeftButton) {
1067 QPoint diff = e->pos() -
m_d->initialDragPanPos;
1068 QPoint offset = QPoint(
m_d->initialDragPanValue.x() - diff.x(),
1069 m_d->initialDragPanValue.y() - diff.y());
1071 const int height =
m_d->layersHeader->defaultSectionSize();
1073 if (
m_d->initialDragPanValue.x() - diff.x() > horizontalScrollBar()->maximum() ||
m_d->initialDragPanValue.x() - diff.x() > horizontalScrollBar()->minimum() ){
1078 horizontalScrollBar()->setValue(offset.x());
1079 verticalScrollBar()->setValue(offset.y() / height);
1083 }
else if (e->buttons() == Qt::MiddleButton) {
1084 QModelIndex index = model()->buddy(indexAt(e->pos()));
1085 if (index.isValid()) {
1086#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1087 QStyleOptionViewItem option = viewOptions();
1089 QStyleOptionViewItem option;
1090 initViewItemOption(&option);
1092 option.rect = visualRect(index);
1094 m_d->tip.showTip(e->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index);
1099 m_d->model->setScrubState(
true);
1100 QTableView::mouseMoveEvent(e);
1106 if (
m_d->modifiersCatcher->modifierPressed(
"pan-zoom")) {
1109 m_d->model->setScrubState(
false);
1110 QTableView::mouseReleaseEvent(e);
1118 if (!indexes.isEmpty() &&
m_d->modifiersCatcher->modifierPressed(
"offset-frame")) {
1120 int leftmostColumn = std::numeric_limits<int>::max();
1122 Q_FOREACH (
const QModelIndex &index, indexes) {
1123 leftmostColumn = qMin(leftmostColumn, index.column());
1124 if (!rows.contains(index.row())) {
1125 rows.append(index.row());
1129 const int lastColumn =
m_d->model->columnCount() - 1;
1131 selectionModel()->clear();
1132 Q_FOREACH (
const int row, rows) {
1133 QItemSelection sel(
m_d->model->index(row, leftmostColumn),
m_d->model->index(row, lastColumn));
1134 selectionModel()->select(sel, QItemSelectionModel::Select);
1137 supportedActions = Qt::MoveAction;
1141 for(
int i = indexes.count() - 1 ; i >= 0; --i) {
1143 indexes.removeAt(i);
1146 selectionModel()->clear();
1148 if (indexes.count() > 0) {
1149 QMimeData *data =
m_d->model->mimeData(indexes);
1153 QPixmap pixmap =
m_d->renderToPixmap(indexes, &rect);
1154 rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
1155 QDrag *drag =
new QDrag(
this);
1156 drag->setPixmap(pixmap);
1157 drag->setMimeData(data);
1158 drag->setHotSpot(
m_d->lastPressedPosition - rect.topLeft());
1159 drag->exec(supportedActions, Qt::MoveAction);
1160 setCurrentIndex(currentIndex());
1173 if (
m_d->lastPressedModifier & Qt::ShiftModifier) {
1200 QModelIndexList selectionBefore = selectionModel()->selectedIndexes();
1201 QModelIndex currentBefore = selectionModel()->currentIndex();
1204 m_d->dragWasSuccessful =
false;
1205 QAbstractItemView::startDrag(supportedActions);
1207 QModelIndex newCurrent;
1208 QPoint selectionOffset;
1210 if (
m_d->dragWasSuccessful) {
1211 newCurrent = currentIndex();
1212 selectionOffset = QPoint(newCurrent.column() - currentBefore.column(),
1213 newCurrent.row() - currentBefore.row());
1215 newCurrent = currentBefore;
1216 selectionOffset = QPoint();
1219 setCurrentIndex(newCurrent);
1220 selectionModel()->clearSelection();
1221 Q_FOREACH (
const QModelIndex &idx, selectionBefore) {
1222 QModelIndex newIndex =
1223 model()->index(idx.row() + selectionOffset.y(),
1224 idx.column() + selectionOffset.x());
1225 selectionModel()->select(newIndex, QItemSelectionModel::Select);
1232 m_d->dragInProgress =
true;
1233 m_d->model->setScrubState(
true);
1235 QTableView::dragEnterEvent(event);
1240 m_d->dragInProgress =
true;
1241 m_d->model->setScrubState(
true);
1243 QAbstractItemView::dragMoveEvent(event);
1248 if (!event->isAccepted() && selectionModel()->isSelected(indexAt(event->pos()))) {
1249 event->setAccepted(
true);
1252 if (event->isAccepted()) {
1253 QModelIndex index = indexAt(event->pos());
1255 if (!
m_d->model->canDropFrameData(event->mimeData(), index)) {
1258 selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
1265 m_d->dragInProgress =
false;
1266 m_d->model->setScrubState(
false);
1268 QAbstractItemView::dragLeaveEvent(event);
1273 m_d->dragInProgress =
false;
1274 m_d->model->setScrubState(
false);
1276 if (event->keyboardModifiers() & Qt::ControlModifier) {
1277 event->setDropAction(Qt::CopyAction);
1278 }
else if (event->keyboardModifiers() & Qt::AltModifier) {
1279 event->setDropAction(Qt::LinkAction);
1282 QAbstractItemView::dropEvent(event);
1285 QModelIndex index = indexAt(event->pos());
1286 if (!event->isAccepted() && selectionModel()->isSelected(index)) {
1287 event->setAccepted(
true);
1288 const Qt::DropAction action =
event->dropAction();
1289 const int row =
event->pos().y();
1290 const int column =
event->pos().x();
1291 if (
m_d->model->dropMimeData(event->mimeData(), action, row, column, index)) {
1292 event->acceptProposedAction();
1296 m_d->dragWasSuccessful =
event->isAccepted();
1301 const int scrollDirection = e->angleDelta().y() > 0 ? 1 : -1;
1302 bool mouseOverLayerPanel = verticalHeader()->geometry().contains(verticalHeader()->mapFromGlobal(e->globalPosition().toPoint()));
1304 if (mouseOverLayerPanel) {
1305 QTableView::wheelEvent(e);
1307 QModelIndex index = currentIndex();
1310 if (index.isValid()) {
1311 column = index.column() + scrollDirection;
1314 if (column >= 0 && !
m_d->dragInProgress) {
1316 setCurrentIndex(
m_d->model->index(index.row(), column));
1331 QTableView::rowsInserted(parent, start, end);
1336 QTableView::currentChanged(current, previous);
1338 if (previous.column() != current.column()) {
1341 if ( current.column() !=
m_d->model->currentTime() ) {
1348 const QEvent *event)
const
1363 (event->type() == QEvent::MouseButtonPress ||
1364 event->type() == QEvent::MouseButtonRelease) &&
1367 const QMouseEvent *mevent =
static_cast<const QMouseEvent*
>(event);
1369 if (mevent->button() == Qt::RightButton &&
1370 selectionModel()->selectedIndexes().contains(index)) {
1373 return QItemSelectionModel::NoUpdate;
1376 if (event->type() == QEvent::MouseButtonPress &&
1377 (mevent->modifiers() & Qt::ControlModifier)) {
1379 return QItemSelectionModel::NoUpdate;
1382 if (event->type() == QEvent::MouseButtonRelease &&
1383 (mevent->modifiers() & Qt::ControlModifier)) {
1385 return QItemSelectionModel::Toggle;
1389 return QAbstractItemView::selectionCommand(index, event);
1395 m_d->horizontalRuler->setFramePerSecond(fps);
1410 for (
int i = 0; i <
m_d->model->rowCount(); i++) {
1414 for (
int column = minColumn; column <= maxColumn; column++) {
1415 indexes <<
m_d->model->index(i, column);
1419 Q_FOREACH (
const QModelIndex &index, selectionModel()->selectedIndexes()) {
1431 minColumn = std::numeric_limits<int>::max();
1432 maxColumn = std::numeric_limits<int>::min();
1434 Q_FOREACH (
const QModelIndex &index, selectionModel()->selectedIndexes()) {
1435 if (!ignoreEditability &&
1438 rows.insert(index.row());
1439 minColumn = qMin(minColumn, index.column());
1440 maxColumn = qMax(maxColumn, index.column());
1447 int minColumn = 0, maxColumn = 0;
1450 if (minColumn > maxColumn)
return;
1453 count = qMax(1, maxColumn - minColumn + 1);
1456 const int insertionColumn =
1458 maxColumn + 1 : minColumn;
1462 for (
int i = 0; i <
m_d->model->rowCount(); i++) {
1468 if (!rows.isEmpty()) {
1469 m_d->model->insertFrames(insertionColumn,
QList<int>(rows.begin(), rows.end()), count, timing);
1478 if (
m_d->insertKeyframeDialog->promptUserSettings(count, timing, direction)) {
1488 if (!entireColumn) {
1489 Q_FOREACH (
const QModelIndex &index, selectionModel()->selectedIndexes()) {
1495 const int column = selectionModel()->currentIndex().column();
1497 for (
int i = 0; i <
m_d->model->rowCount(); i++) {
1498 const QModelIndex index =
m_d->model->index(i, column);
1505 if (!indexes.isEmpty()) {
1506 m_d->model->insertHoldFrames(indexes, count);
1521 const int count = QInputDialog::getInt(
this,
1522 i18nc(
"@title:window",
"Insert or Remove Hold Frames"),
1523 i18nc(
"@label:spinbox",
"Enter number of frames"),
1525 m_d->insertKeyframeDialog->defaultTimingOfAddedFrames() :
1526 m_d->insertKeyframeDialog->defaultNumberOfHoldFramesToRemove(),
1531 m_d->insertKeyframeDialog->setDefaultTimingOfAddedFrames(count);
1534 m_d->insertKeyframeDialog->setDefaultNumberOfHoldFramesToRemove(count);
1542 QMap<int, QList<int>> indexMap;
1546 foreach (
const QModelIndex &index, selectedIndices) {
1547 if (!indexMap.contains(index.row())) {
1552 indexMap[index.row()] << index.column();
1557 selectionModel()->clearSelection();
1558 foreach (
const int &layer, indexMap.keys()) {
1560 int progressIndex = 0;
1562 std::sort(indexMap[layer].begin(), indexMap[layer].end());
1563 for (it = indexMap[layer].constBegin(); it != indexMap[layer].constEnd(); it++) {
1564 const int offsetColumn = *it + (progressIndex * count);
1565 selectionModel()->select(model()->index(layer, offsetColumn), QItemSelectionModel::Select);
1574 if (selectedIndices.isEmpty())
return;
1576 int minColumn = std::numeric_limits<int>::max();
1577 int minRow = std::numeric_limits<int>::max();
1578 Q_FOREACH (
const QModelIndex &index, selectedIndices) {
1579 minRow = qMin(minRow, index.row());
1580 minColumn = qMin(minColumn, index.column());
1583 const QModelIndex baseIndex =
m_d->model->index(minRow, minColumn);
1584 QMimeData *data =
m_d->model->mimeDataExtended(selectedIndices,
1591 QClipboard *cb = QApplication::clipboard();
1592 cb->setMimeData(data);
1599 if (selectedIndices.isEmpty())
return;
1601 int minColumn = std::numeric_limits<int>::max();
1602 int minRow = std::numeric_limits<int>::max();
1603 Q_FOREACH (
const QModelIndex &index, selectedIndices) {
1604 minRow = qMin(minRow, index.row());
1605 minColumn = qMin(minColumn, index.column());
1608 const QModelIndex baseIndex =
m_d->model->index(minRow, minColumn);
1609 QMimeData *data =
m_d->model->mimeDataExtended(selectedIndices,
1614 QClipboard *cb = QApplication::clipboard();
1615 cb->setMimeData(data);
1628 bool selectionExists = minColumn != maxColumn;
1630 menu->addSection(i18n(
"Edit Frames:"));
1631 menu->addSeparator();
1633 if (selectionExists) {
1640 menu->addSeparator();
1650 if (!emptyFrame && cloneFrameSelected) {
1654 menu->addSeparator();
1657 QMenu *frames = menu->addMenu(i18nc(
"@item:inmenu",
"Keyframes"));
1660 frames->addSeparator();
1665 QMenu *hold = menu->addMenu(i18nc(
"@item:inmenu",
"Hold Frames"));
1668 hold->addSeparator();
1673 menu->addSeparator();
1680 menu->addSeparator();
1685 menu->addSeparator();
1690 const int sectionWidth =
m_d->horizontalRuler->defaultSectionSize();
1691 return sectionWidth * column;
1696#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1697 QStyleOptionViewItem option =
q->viewOptions();
1699 QStyleOptionViewItem option;
1700 q->initViewItemOption(&option);
1702 option.locale =
q->locale();
1703 option.locale.setNumberOptions(QLocale::OmitGroupSeparator);
1712 const QRect viewportRect = q->viewport()->rect();
1714 for (
int i = 0; i < indexes.count(); ++i) {
1715 const QModelIndex &index = indexes.at(i);
1716 const QRect current = q->visualRect(index);
1717 if (current.intersects(viewportRect)) {
1718 ret += qMakePair(current, index);
1722 rect &= viewportRect;
1731 if (paintPairs.isEmpty())
1734 QPixmap pixmap(r->size());
1735 pixmap.fill(Qt::transparent);
1737 QPainter painter(&pixmap);
1739 QStyleOptionViewItem option = viewOptionsV4();
1740 option.state |= QStyle::State_Selected;
1742 for (
int j = 0; j < paintPairs.count(); ++j) {
1743 option.rect = paintPairs.at(j).first.translated(-r->topLeft());
1744 const QModelIndex ¤t = paintPairs.at(j).second;
1747#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1748 q->itemDelegate(current)->paint(&painter, option, current);
1750 q->itemDelegateForIndex(current)->paint(&painter, option, current);
1762 buttonSize = QSize(minimalSize, minimalSize);
1770 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
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