File indexing completed on 2024-12-22 04:14:44

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisAnimTimelineFramesView.h"
0008 
0009 #include "KisAnimTimelineFramesModel.h"
0010 #include "KisAnimTimelineTimeHeader.h"
0011 #include "KisAnimTimelineLayersHeader.h"
0012 #include "timeline_insert_keyframe_dialog.h"
0013 #include "KisAnimTimelineFrameDelegate.h"
0014 #include "KisPlaybackEngine.h"
0015 
0016 #include <QPainter>
0017 #include <QApplication>
0018 #include <QDropEvent>
0019 #include <QMenu>
0020 #include <QScrollBar>
0021 #include <QScroller>
0022 #include <QDrag>
0023 #include <QKeySequence>
0024 #include <QInputDialog>
0025 #include <QClipboard>
0026 #include <QMimeData>
0027 #include <QLayout>
0028 #include <QScreen>
0029 
0030 #include "KSharedConfig"
0031 #include "KisKineticScroller.h"
0032 
0033 #include "kis_zoom_button.h"
0034 #include "kis_icon_utils.h"
0035 #include "KisAnimUtils.h"
0036 #include "KisCanvasAnimationState.h"
0037 #include "kis_canvas2.h"
0038 #include "kis_custom_modifiers_catcher.h"
0039 #include "kis_action.h"
0040 #include "kis_signal_compressor.h"
0041 #include "kis_time_span.h"
0042 #include "kis_color_label_selector_widget.h"
0043 #include "kis_layer_filter_widget.h"
0044 #include "kis_keyframe_channel.h"
0045 #include "kis_slider_spin_box.h"
0046 #include "kis_signals_blocker.h"
0047 #include "kis_image_config.h"
0048 #include "widgets/kis_zoom_scrollbar.h"
0049 #include "KisImportExportManager.h"
0050 #include "KoFileDialog.h"
0051 #include "KisIconToolTip.h"
0052 
0053 typedef QPair<QRect, QModelIndex> QItemViewPaintPair;
0054 typedef QList<QItemViewPaintPair> QItemViewPaintPairs;
0055 
0056 void resizeToMinimalSize(QAbstractButton *w, int minimalSize);
0057 inline bool isIndexDragEnabled(QAbstractItemModel *model, const QModelIndex &index);
0058 
0059 struct KisAnimTimelineFramesView::Private
0060 {
0061     Private(KisAnimTimelineFramesView *_q)
0062         : q(_q)
0063         , model(nullptr)
0064         , horizontalRuler(nullptr)
0065         , layersHeader(nullptr)
0066         , addLayersButton(nullptr)
0067         , pinLayerToTimelineAction(nullptr)
0068         , colorSelector(nullptr)
0069         , colorSelectorAction(nullptr)
0070         , multiframeColorSelector(nullptr)
0071         , multiframeColorSelectorAction(nullptr)
0072         , layerEditingMenu(nullptr)
0073         , existingLayersMenu(nullptr)
0074         , insertKeyframeDialog(nullptr)
0075         , zoomDragButton(nullptr)
0076         , canvas(nullptr)
0077         , fps(1)
0078         , dragInProgress(false)
0079         , dragWasSuccessful(false)
0080         , modifiersCatcher(0)
0081         , kineticScrollInfiniteFrameUpdater()
0082         , selectionChangedCompressor(300, KisSignalCompressor::FIRST_INACTIVE)
0083     {
0084         kineticScrollInfiniteFrameUpdater.setTimerType(Qt::CoarseTimer);
0085     }
0086 
0087     KisAnimTimelineFramesView *q;
0088     KisAnimTimelineFramesModel *model;
0089 
0090     KisAnimTimelineTimeHeader *horizontalRuler;
0091     KisAnimTimelineLayersHeader *layersHeader;
0092 
0093     QToolButton* addLayersButton;
0094     KisAction* pinLayerToTimelineAction;
0095 
0096     KisColorLabelSelectorWidgetMenuWrapper* colorSelector;
0097     QWidgetAction* colorSelectorAction;
0098     KisColorLabelSelectorWidgetMenuWrapper* multiframeColorSelector;
0099     QWidgetAction* multiframeColorSelectorAction;
0100 
0101     QMenu* layerEditingMenu;
0102     QMenu* existingLayersMenu;
0103     TimelineInsertKeyframeDialog* insertKeyframeDialog;
0104     KisZoomButton* zoomDragButton;
0105 
0106     KoCanvasBase* canvas;
0107 
0108     int fps;
0109     QPoint initialDragPanValue;
0110     QPoint initialDragPanPos;
0111 
0112     bool dragInProgress;
0113     bool dragWasSuccessful;
0114 
0115     KisCustomModifiersCatcher *modifiersCatcher;
0116     QPoint lastPressedPosition;
0117     Qt::KeyboardModifiers lastPressedModifier;
0118 
0119     QTimer kineticScrollInfiniteFrameUpdater;
0120 
0121     KisSignalCompressor selectionChangedCompressor;
0122 
0123     QStyleOptionViewItem viewOptionsV4() const;
0124     QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const;
0125     QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r) const;
0126 
0127     KisIconToolTip tip;
0128 
0129     KisActionManager *actionMan = 0;
0130 };
0131 
0132 KisAnimTimelineFramesView::KisAnimTimelineFramesView(QWidget *parent)
0133     : QTableView(parent),
0134       m_d(new Private(this))
0135 {
0136     m_d->modifiersCatcher = new KisCustomModifiersCatcher(this);
0137     m_d->modifiersCatcher->addModifier("pan-zoom", Qt::Key_Space);
0138     m_d->modifiersCatcher->addModifier("offset-frame", Qt::Key_Shift);
0139 
0140     setCornerButtonEnabled(false);
0141     setSelectionBehavior(QAbstractItemView::SelectItems);
0142     setSelectionMode(QAbstractItemView::ExtendedSelection);
0143 
0144     setItemDelegate(new KisAnimTimelineFrameDelegate(this));
0145 
0146     setDragEnabled(true);
0147     setDragDropMode(QAbstractItemView::DragDrop);
0148     setAcceptDrops(true);
0149     setDropIndicatorShown(true);
0150     setDefaultDropAction(Qt::MoveAction);
0151 
0152     m_d->horizontalRuler = new KisAnimTimelineTimeHeader(this);
0153     this->setHorizontalHeader(m_d->horizontalRuler);
0154 
0155     connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnLeft()), SLOT(slotInsertKeyframeColumnLeft()));
0156     connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnRight()), SLOT(slotInsertKeyframeColumnRight()));
0157 
0158     connect(m_d->horizontalRuler, SIGNAL(sigInsertMultipleColumns()), SLOT(slotInsertMultipleKeyframeColumns()));
0159 
0160     connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumns()), SLOT(slotRemoveSelectedColumns()));
0161     connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumnsAndShift()), SLOT(slotRemoveSelectedColumnsAndShift()));
0162 
0163     connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumns()), SLOT(slotInsertHoldFrameColumn()));
0164     connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumns()), SLOT(slotRemoveHoldFrameColumn()));
0165 
0166     connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumnsCustom()), SLOT(slotInsertMultipleHoldFrameColumns()));
0167     connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumnsCustom()), SLOT(slotRemoveMultipleHoldFrameColumns()));
0168 
0169     connect(m_d->horizontalRuler, SIGNAL(sigMirrorColumns()), SLOT(slotMirrorColumns()));
0170     connect(m_d->horizontalRuler, SIGNAL(sigClearCache()), SLOT(slotClearCache()));
0171 
0172     connect(m_d->horizontalRuler, SIGNAL(sigCopyColumns()), SLOT(slotCopyColumns()));
0173     connect(m_d->horizontalRuler, SIGNAL(sigCutColumns()), SLOT(slotCutColumns()));
0174     connect(m_d->horizontalRuler, SIGNAL(sigPasteColumns()), SLOT(slotPasteColumns()));
0175 
0176     m_d->layersHeader = new KisAnimTimelineLayersHeader(this);
0177 
0178     m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed);
0179 
0180     m_d->layersHeader->setDefaultSectionSize(24);
0181     m_d->layersHeader->setMinimumWidth(60);
0182     m_d->layersHeader->setHighlightSections(true);
0183     this->setVerticalHeader(m_d->layersHeader);
0184 
0185     /********** Layer Menu ***********************************************************/
0186 
0187     m_d->layerEditingMenu = new QMenu(this);
0188     m_d->layerEditingMenu->addSection(i18n("Edit Layers:"));
0189     m_d->layerEditingMenu->addSeparator();
0190 
0191     m_d->layerEditingMenu->addAction(KisAnimUtils::newLayerActionName, this, SLOT(slotAddNewLayer()));
0192     m_d->layerEditingMenu->addAction(KisAnimUtils::removeLayerActionName, this, SLOT(slotRemoveLayer()));
0193     m_d->layerEditingMenu->addSeparator();
0194     m_d->existingLayersMenu = m_d->layerEditingMenu->addMenu(KisAnimUtils::pinExistingLayerActionName);
0195 
0196     connect(m_d->existingLayersMenu, SIGNAL(aboutToShow()), SLOT(slotUpdateLayersMenu()));
0197     connect(m_d->existingLayersMenu, SIGNAL(triggered(QAction*)), SLOT(slotAddExistingLayer(QAction*)));
0198 
0199     connect(m_d->layersHeader, SIGNAL(sigRequestContextMenu(QPoint)), SLOT(slotLayerContextMenuRequested(QPoint)));
0200 
0201     m_d->addLayersButton = new QToolButton(this);
0202     m_d->addLayersButton->setAutoRaise(true);
0203     m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("list-add-22"));
0204     m_d->addLayersButton->setIconSize(QSize(22, 22));
0205     m_d->addLayersButton->setPopupMode(QToolButton::InstantPopup);
0206     m_d->addLayersButton->setMenu(m_d->layerEditingMenu);
0207 
0208     /********** Frame Editing Context Menu ***********************************************/
0209 
0210     m_d->colorSelector = new KisColorLabelSelectorWidgetMenuWrapper(this);
0211     MouseClickIgnore* clickIgnore = new MouseClickIgnore(m_d->colorSelector);
0212     m_d->colorSelector->installEventFilter(clickIgnore);
0213     m_d->colorSelectorAction = new QWidgetAction(this);
0214     m_d->colorSelectorAction->setDefaultWidget(m_d->colorSelector);
0215     connect(m_d->colorSelector->colorLabelSelector(), &KisColorLabelSelectorWidget::currentIndexChanged, this, &KisAnimTimelineFramesView::slotColorLabelChanged);
0216 
0217     m_d->multiframeColorSelector = new KisColorLabelSelectorWidgetMenuWrapper(this);
0218     m_d->multiframeColorSelector->installEventFilter(clickIgnore);
0219     m_d->multiframeColorSelectorAction = new QWidgetAction(this);
0220     m_d->multiframeColorSelectorAction->setDefaultWidget(m_d->multiframeColorSelector);
0221     connect(m_d->multiframeColorSelector->colorLabelSelector(), &KisColorLabelSelectorWidget::currentIndexChanged, this, &KisAnimTimelineFramesView::slotColorLabelChanged);
0222 
0223     /********** Insert Keyframes Dialog **************************************************/
0224 
0225     m_d->insertKeyframeDialog = new TimelineInsertKeyframeDialog(this);
0226 
0227     /********** Zoom Button **************************************************************/
0228 
0229     m_d->zoomDragButton = new KisZoomButton(this);
0230     m_d->zoomDragButton->setAutoRaise(true);
0231     m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal"));
0232     m_d->zoomDragButton->setIconSize(QSize(22, 22));
0233 
0234     m_d->zoomDragButton->setToolTip(i18nc("@info:tooltip", "Zoom Timeline. Hold down and drag left or right."));
0235     m_d->zoomDragButton->setPopupMode(QToolButton::InstantPopup);
0236     connect(m_d->zoomDragButton, SIGNAL(zoom(qreal)), SLOT(slotZoom(qreal)));
0237 
0238     /********** Zoom Scrollbar **************************************************************/
0239 
0240     KisZoomableScrollBar* hZoomableBar = new KisZoomableScrollBar(this);
0241     setHorizontalScrollBar(hZoomableBar);
0242     setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
0243     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0244     setVerticalScrollBar(new KisZoomableScrollBar(this));
0245     hZoomableBar->setEnabled(false);
0246 
0247     connect(hZoomableBar, &KisZoomableScrollBar::valueChanged, m_d->horizontalRuler, &KisAnimTimelineTimeHeader::setPixelOffset);
0248     connect(hZoomableBar, SIGNAL(zoom(qreal)), this, SLOT(slotZoom(qreal)));
0249     connect(hZoomableBar, SIGNAL(overscroll(qreal)), SLOT(slotUpdateInfiniteFramesCount()));
0250     connect(hZoomableBar, SIGNAL(sliderReleased()), SLOT(slotUpdateInfiniteFramesCount()));
0251 
0252     /********** Kinetic Scrolling **************************************************************/
0253 
0254     {
0255         QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this);
0256         if (scroller) {
0257             connect(scroller, SIGNAL(stateChanged(QScroller::State)),
0258                     this, SLOT(slotScrollerStateChanged(QScroller::State)));
0259 
0260             connect(&m_d->kineticScrollInfiniteFrameUpdater, &QTimer::timeout, [this, scroller](){
0261                 slotUpdateInfiniteFramesCount();
0262                 scroller->resendPrepareEvent();
0263             });
0264 
0265             QScrollerProperties props = scroller->scrollerProperties();
0266             props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
0267             props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
0268             scroller->setScrollerProperties(props);
0269         }
0270     }
0271 
0272     connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()),
0273             SLOT(slotSelectionChanged()));
0274     connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()),
0275             SLOT(slotUpdateFrameActions()));
0276 
0277     {
0278         QClipboard *cb = QApplication::clipboard();
0279         connect(cb, SIGNAL(dataChanged()), SLOT(slotUpdateFrameActions()));
0280     }
0281 
0282     setFramesPerSecond(12);
0283 }
0284 
0285 KisAnimTimelineFramesView::~KisAnimTimelineFramesView()
0286 {
0287 }
0288 
0289 void KisAnimTimelineFramesView::setModel(QAbstractItemModel *model)
0290 {
0291     KisAnimTimelineFramesModel *framesModel = qobject_cast<KisAnimTimelineFramesModel*>(model);
0292     m_d->model = framesModel;
0293 
0294     QTableView::setModel(model);
0295 
0296     connect(m_d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
0297             this, SLOT(slotHeaderDataChanged(Qt::Orientation,int,int)));
0298 
0299     connect(m_d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0300             this, SLOT(slotDataChanged(QModelIndex,QModelIndex)));
0301 
0302     connect(m_d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
0303             this, SLOT(slotReselectCurrentIndex()));
0304 
0305     connect(m_d->model, SIGNAL(sigInfiniteTimelineUpdateNeeded()),
0306             this, SLOT(slotUpdateInfiniteFramesCount()));
0307     
0308     connect(m_d->model, SIGNAL(requestTransferSelectionBetweenRows(int,int)),
0309             this, SLOT(slotTryTransferSelectionBetweenRows(int,int)));
0310 
0311     connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
0312             &m_d->selectionChangedCompressor, SLOT(start()));
0313 
0314     connect(m_d->model, SIGNAL(sigEnsureRowVisible(int)), SLOT(slotEnsureRowVisible(int)));
0315 }
0316 
0317 void KisAnimTimelineFramesView::setActionManager(KisActionManager *actionManager)
0318 {
0319     m_d->actionMan = actionManager;
0320     m_d->horizontalRuler->setActionManager(actionManager);
0321 
0322     if (actionManager) {
0323         KisAction *action = 0;
0324 
0325         action = m_d->actionMan->createAction("add_blank_frame");
0326         connect(action, SIGNAL(triggered()), SLOT(slotAddBlankFrame()));
0327 
0328         action = m_d->actionMan->createAction("add_duplicate_frame");
0329         connect(action, SIGNAL(triggered()), SLOT(slotAddDuplicateFrame()));
0330 
0331         action = m_d->actionMan->createAction("insert_keyframe_left");
0332         connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframeLeft()));
0333 
0334         action = m_d->actionMan->createAction("insert_keyframe_right");
0335         connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframeRight()));
0336 
0337         action = m_d->actionMan->createAction("insert_multiple_keyframes");
0338         connect(action, SIGNAL(triggered()), SLOT(slotInsertMultipleKeyframes()));
0339 
0340         action = m_d->actionMan->createAction("remove_frames_and_pull");
0341         connect(action, SIGNAL(triggered()), SLOT(slotRemoveSelectedFramesAndShift()));
0342 
0343         action = m_d->actionMan->createAction("remove_frames");
0344         connect(action, SIGNAL(triggered()), SLOT(slotRemoveSelectedFrames()));
0345 
0346         action = m_d->actionMan->createAction("insert_hold_frame");
0347         connect(action, SIGNAL(triggered()), SLOT(slotInsertHoldFrame()));
0348 
0349         action = m_d->actionMan->createAction("insert_multiple_hold_frames");
0350         connect(action, SIGNAL(triggered()), SLOT(slotInsertMultipleHoldFrames()));
0351 
0352         action = m_d->actionMan->createAction("remove_hold_frame");
0353         connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFrame()));
0354 
0355         action = m_d->actionMan->createAction("remove_multiple_hold_frames");
0356         connect(action, SIGNAL(triggered()), SLOT(slotRemoveMultipleHoldFrames()));
0357 
0358         action = m_d->actionMan->createAction("mirror_frames");
0359         connect(action, SIGNAL(triggered()), SLOT(slotMirrorFrames()));
0360 
0361         action = m_d->actionMan->createAction("copy_frames");
0362         connect(action, SIGNAL(triggered()), SLOT(slotCopyFrames()));
0363 
0364         action = m_d->actionMan->createAction("copy_frames_as_clones");
0365         connect(action, &KisAction::triggered, [this](){clone(false);});
0366 
0367         action = m_d->actionMan->createAction("make_clones_unique");
0368         connect(action, SIGNAL(triggered()), SLOT(slotMakeClonesUnique()));
0369 
0370         action = m_d->actionMan->createAction("cut_frames");
0371         connect(action, SIGNAL(triggered()), SLOT(slotCutFrames()));
0372 
0373         action = m_d->actionMan->createAction("paste_frames");
0374         connect(action, SIGNAL(triggered()), SLOT(slotPasteFrames()));
0375 
0376         action = m_d->actionMan->createAction("set_start_time");
0377         connect(action, SIGNAL(triggered()), SLOT(slotSetStartTimeToCurrentPosition()));
0378 
0379         action = m_d->actionMan->createAction("set_end_time");
0380         connect(action, SIGNAL(triggered()), SLOT(slotSetEndTimeToCurrentPosition()));
0381 
0382         action = m_d->actionMan->createAction("update_playback_range");
0383         connect(action, SIGNAL(triggered()), SLOT(slotUpdatePlaybackRange()));
0384 
0385         action = m_d->actionMan->actionByName("pin_to_timeline");
0386         m_d->pinLayerToTimelineAction = action;
0387         m_d->layerEditingMenu->addAction(action);
0388     }
0389 }
0390 
0391 void KisAnimTimelineFramesView::updateGeometries()
0392 {
0393     QTableView::updateGeometries();
0394 
0395     const int availableHeight = m_d->horizontalRuler->height();
0396     const int margin = 2;
0397     const int minimalSize = availableHeight - 2 * margin;
0398 
0399     resizeToMinimalSize(m_d->addLayersButton, minimalSize);
0400     resizeToMinimalSize(m_d->zoomDragButton, minimalSize);
0401 
0402     int x = 2 * margin;
0403     int y = (availableHeight - minimalSize) / 2;
0404     m_d->addLayersButton->move(x, 2 * y);
0405 
0406     const int availableWidth = m_d->layersHeader->width();
0407 
0408     x = availableWidth - margin - minimalSize;
0409     m_d->zoomDragButton->move(x, 2 * y);
0410 }
0411 
0412 void KisAnimTimelineFramesView::slotCanvasUpdate(KoCanvasBase *canvas)
0413 {
0414     if (m_d->canvas) {
0415         KisCanvas2* canvas2 = dynamic_cast<KisCanvas2*>(m_d->canvas);
0416         if (canvas2) {
0417             KisCanvasAnimationState* state = canvas2->animationState();
0418             state->disconnect(this);
0419         }
0420     }
0421 
0422     m_d->canvas = canvas;
0423 
0424     horizontalScrollBar()->setEnabled(m_d->canvas != nullptr);
0425 }
0426 
0427 void KisAnimTimelineFramesView::slotUpdateIcons()
0428 {
0429     m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("list-add-22"));
0430     m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal"));
0431 }
0432 
0433 void KisAnimTimelineFramesView::slotUpdateLayersMenu()
0434 {
0435     QAction *action = 0;
0436 
0437     m_d->existingLayersMenu->clear();
0438 
0439     QVariant value = model()->headerData(0, Qt::Vertical, KisAnimTimelineFramesModel::OtherLayersRole);
0440     if (value.isValid()) {
0441         KisAnimTimelineFramesModel::OtherLayersList list = value.value<KisAnimTimelineFramesModel::OtherLayersList>();
0442 
0443         int i = 0;
0444         Q_FOREACH (const KisAnimTimelineFramesModel::OtherLayer &l, list) {
0445             action = m_d->existingLayersMenu->addAction(l.name);
0446             action->setData(i++);
0447         }
0448     }
0449 }
0450 
0451 void KisAnimTimelineFramesView::slotUpdateFrameActions()
0452 {
0453     if (!m_d->actionMan) return;
0454 
0455     const QModelIndexList editableIndexes = calculateSelectionSpan(false, true);
0456     const bool hasEditableFrames = !editableIndexes.isEmpty();
0457 
0458     bool hasExistingFrames = false;
0459     Q_FOREACH (const QModelIndex &index, editableIndexes) {
0460         if (model()->data(index, KisAnimTimelineFramesModel::FrameExistsRole).toBool()) {
0461             hasExistingFrames = true;
0462             break;
0463         }
0464     }
0465 
0466     auto enableAction = [this] (const QString &id, bool value) {
0467         KisAction *action = m_d->actionMan->actionByName(id);
0468         KIS_SAFE_ASSERT_RECOVER_RETURN(action);
0469         action->setEnabled(value);
0470     };
0471 
0472     enableAction("add_blank_frame", hasEditableFrames);
0473     enableAction("add_duplicate_frame", hasEditableFrames);
0474 
0475     enableAction("insert_keyframe_left", hasEditableFrames);
0476     enableAction("insert_keyframe_right", hasEditableFrames);
0477     enableAction("insert_multiple_keyframes", hasEditableFrames);
0478 
0479     enableAction("remove_frames", hasEditableFrames && hasExistingFrames);
0480     enableAction("remove_frames_and_pull", hasEditableFrames);
0481 
0482     enableAction("insert_hold_frame", hasEditableFrames);
0483     enableAction("insert_multiple_hold_frames", hasEditableFrames);
0484 
0485     enableAction("remove_hold_frame", hasEditableFrames);
0486     enableAction("remove_multiple_hold_frames", hasEditableFrames);
0487 
0488     enableAction("mirror_frames", hasEditableFrames && editableIndexes.size() > 1);
0489 
0490     enableAction("copy_frames", true);
0491     enableAction("cut_frames", hasEditableFrames);
0492 }
0493 
0494 void KisAnimTimelineFramesView::slotSelectionChanged()
0495 {
0496     int minColumn = std::numeric_limits<int>::max();
0497     int maxColumn = std::numeric_limits<int>::min();    
0498 
0499     calculateActiveLayerSelectedTimes(selectedIndexes());
0500 
0501     foreach (const QModelIndex &idx, selectedIndexes()) {
0502         if (idx.column() > maxColumn) {
0503             maxColumn = idx.column();
0504         }
0505 
0506         if (idx.column() < minColumn) {
0507             minColumn = idx.column();
0508         }
0509     }
0510 
0511     KisTimeSpan range;
0512     if (maxColumn > minColumn) {
0513         range = KisTimeSpan::fromTimeWithDuration(minColumn, maxColumn - minColumn + 1);
0514     }
0515 
0516     m_d->model->setPlaybackRange(range);
0517 }
0518 
0519 void KisAnimTimelineFramesView::slotReselectCurrentIndex()
0520 {
0521     QModelIndex index = currentIndex();
0522     currentChanged(index, index);
0523 }
0524 
0525 void KisAnimTimelineFramesView::slotTryTransferSelectionBetweenRows(int fromRow, int toRow)
0526 {
0527     //If there's only one selected index, or less, just select the current index if valid...
0528     QModelIndex current = model()->index(toRow, m_d->model->currentTime());
0529     if (selectedIndexes().count() <= 1) {
0530         if (selectedIndexes().count() != 1 || 
0531             (selectedIndexes().first().column() == current.column() &&
0532             selectedIndexes().first().row() == fromRow)) {
0533             setCurrentIndex(current);
0534         }
0535         
0536     }
0537 }
0538 
0539 void KisAnimTimelineFramesView::slotSetStartTimeToCurrentPosition()
0540 {
0541      m_d->model->setDocumentClipRangeStart(this->currentIndex().column());
0542 }
0543 
0544 void KisAnimTimelineFramesView::slotSetEndTimeToCurrentPosition()
0545 {
0546     m_d->model->setDocumentClipRangeEnd(this->currentIndex().column());
0547 }
0548 
0549 void KisAnimTimelineFramesView::slotUpdatePlaybackRange()
0550 {
0551     QSet<int> rows;
0552     int minColumn = 0;
0553     int maxColumn = 0;
0554 
0555     calculateSelectionMetrics(minColumn, maxColumn, rows);
0556 
0557     m_d->model->setDocumentClipRangeStart(minColumn);
0558     m_d->model->setDocumentClipRangeEnd(maxColumn);
0559 }
0560 
0561 void KisAnimTimelineFramesView::slotUpdateInfiniteFramesCount()
0562 {
0563     const int lastVisibleFrame = m_d->horizontalRuler->estimateLastVisibleColumn();
0564     m_d->model->setLastVisibleFrame(lastVisibleFrame);
0565 }
0566 
0567 void KisAnimTimelineFramesView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0568 {
0569     if (m_d->model->isPlaybackActive()) return;
0570 
0571     int selectedColumn = -1;
0572 
0573     for (int j = topLeft.column(); j <= bottomRight.column(); j++) {
0574         QVariant value = m_d->model->data(
0575                     m_d->model->index(topLeft.row(), j),
0576                     KisAnimTimelineFramesModel::ActiveFrameRole);
0577 
0578         if (value.isValid() && value.toBool()) {
0579             selectedColumn = j;
0580             break;
0581         }
0582     }
0583 
0584     QModelIndex index = currentIndex();
0585 
0586     if (!index.isValid() && selectedColumn < 0) {
0587         return;
0588     }
0589 
0590     if (selectionModel()->selectedIndexes().count() > 1) return;
0591 
0592     if (selectedColumn == -1) {
0593         selectedColumn = index.column();
0594     }
0595 
0596     if (selectedColumn != index.column() && !m_d->dragInProgress && !m_d->model->isScrubbing()) {
0597         int row = index.isValid() ? index.row() : 0;
0598         // Todo: This is causing double audio pushes. We should fix this eventually.
0599         selectionModel()->setCurrentIndex(m_d->model->index(row, selectedColumn), QItemSelectionModel::ClearAndSelect);
0600     }
0601 }
0602 
0603 void KisAnimTimelineFramesView::slotHeaderDataChanged(Qt::Orientation orientation, int first, int last)
0604 {
0605     Q_UNUSED(first);
0606     Q_UNUSED(last);
0607 
0608     if (orientation == Qt::Horizontal) {
0609         const int newFps = m_d->model->headerData(0, Qt::Horizontal, KisAnimTimelineFramesModel::FramesPerSecondRole).toInt();
0610 
0611         if (newFps != m_d->fps) {
0612             setFramesPerSecond(newFps);
0613         }
0614     } else {
0615         calculateActiveLayerSelectedTimes(selectedIndexes());
0616     }
0617 }
0618 
0619 void KisAnimTimelineFramesView::slotColorLabelChanged(int label)
0620 {
0621     Q_FOREACH(QModelIndex index, selectedIndexes()) {
0622         m_d->model->setData(index, label, KisAnimTimelineFramesModel::FrameColorLabelIndexRole);
0623     }
0624     KisImageConfig(false).setDefaultFrameColorLabel(label);
0625 }
0626 
0627 void KisAnimTimelineFramesView::slotAddNewLayer()
0628 {
0629     QModelIndex index = currentIndex();
0630     const int newRow = index.isValid() ? index.row() : 0;
0631     model()->insertRow(newRow);
0632 }
0633 
0634 void KisAnimTimelineFramesView::slotAddExistingLayer(QAction *action)
0635 {
0636     QVariant value = action->data();
0637 
0638     if (value.isValid()) {
0639         QModelIndex index = currentIndex();
0640         const int newRow = index.isValid() ? index.row() + 1 : 0;
0641 
0642         m_d->model->insertOtherLayer(value.toInt(), newRow);
0643     }
0644 }
0645 
0646 void KisAnimTimelineFramesView::slotRemoveLayer()
0647 {
0648     QModelIndex index = currentIndex();
0649     if (!index.isValid()) return;
0650     model()->removeRow(index.row());
0651 }
0652 
0653 void KisAnimTimelineFramesView::slotLayerContextMenuRequested(const QPoint &globalPos)
0654 {
0655     m_d->layerEditingMenu->exec(globalPos);
0656 }
0657 
0658 void KisAnimTimelineFramesView::slotAddBlankFrame()
0659 {
0660     QModelIndexList selectedIndices = calculateSelectionSpan(false);
0661     Q_FOREACH(const QModelIndex &index, selectedIndices) {
0662         if (!index.isValid() ||
0663             !m_d->model->data(index, KisAnimTimelineFramesModel::FrameEditableRole).toBool()) {
0664             selectedIndices.removeOne(index);
0665         }
0666     }
0667 
0668     m_d->model->createFrame(selectedIndices);
0669 }
0670 
0671 void KisAnimTimelineFramesView::slotAddDuplicateFrame()
0672 {
0673     QModelIndex index = currentIndex();
0674     if (!index.isValid() ||
0675         !m_d->model->data(index, KisAnimTimelineFramesModel::FrameEditableRole).toBool()) {
0676 
0677         return;
0678     }
0679 
0680     m_d->model->copyFrame(index);
0681 }
0682 
0683 void KisAnimTimelineFramesView::slotRemoveSelectedFrames(bool entireColumn, bool pull)
0684 {
0685     const QModelIndexList selectedIndices = calculateSelectionSpan(entireColumn);
0686 
0687     if (!selectedIndices.isEmpty()) {
0688         if (pull) {
0689             m_d->model->removeFramesAndOffset(selectedIndices);
0690         } else {
0691             m_d->model->removeFrames(selectedIndices);
0692         }
0693     }
0694 }
0695 
0696 void KisAnimTimelineFramesView::slotMirrorFrames(bool entireColumn)
0697 {
0698     const QModelIndexList indexes = calculateSelectionSpan(entireColumn);
0699 
0700     if (!indexes.isEmpty()) {
0701         m_d->model->mirrorFrames(indexes);
0702     }
0703 }
0704 
0705 void KisAnimTimelineFramesView::slotClearCache() {
0706     m_d->model->clearEntireCache();
0707 }
0708 
0709 void KisAnimTimelineFramesView::slotPasteFrames(bool entireColumn)
0710 {
0711     const QModelIndex currentIndex =
0712         !entireColumn ? this->currentIndex() : m_d->model->index(0, this->currentIndex().column());
0713 
0714     if (!currentIndex.isValid()) return;
0715 
0716     QClipboard *cb = QApplication::clipboard();
0717     const QMimeData *data = cb->mimeData();
0718 
0719     if (data && data->hasFormat("application/x-krita-frame")) {
0720 
0721         bool dataMoved = false;
0722         bool result = m_d->model->dropMimeDataExtended(data, Qt::MoveAction, currentIndex, &dataMoved);
0723 
0724         if (result && dataMoved) {
0725             cb->clear();
0726         }
0727     }
0728 }
0729 
0730 void KisAnimTimelineFramesView::slotMakeClonesUnique()
0731 {
0732     if (!m_d->model) return;
0733 
0734     const QModelIndexList indices = calculateSelectionSpan(false);
0735     m_d->model->makeClonesUnique(indices);
0736 }
0737 
0738 void KisAnimTimelineFramesView::slotSelectAudioChannelFile()
0739 {
0740     if (!m_d->model) return;
0741 
0742     QString defaultDir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
0743 
0744     const QString currentFile = m_d->model->audioChannelFileName();
0745     QDir baseDir = QFileInfo(currentFile).absoluteDir();
0746     if (baseDir.exists()) {
0747         defaultDir = baseDir.absolutePath();
0748     }
0749 
0750     const QString result = KisImportExportManager::askForAudioFileName(defaultDir, this);
0751     const QFileInfo info(result);
0752 
0753     if (info.exists()) {
0754         m_d->model->setAudioChannelFileName(info);
0755     }
0756 }
0757 
0758 void KisAnimTimelineFramesView::slotAudioChannelMute(bool value)
0759 {
0760     if (!m_d->model) return;
0761 
0762     if (value != m_d->model->isAudioMuted()) {
0763         m_d->model->setAudioMuted(value);
0764     }
0765 }
0766 
0767 void KisAnimTimelineFramesView::slotAudioChannelRemove()
0768 {
0769     if (!m_d->model) return;
0770     m_d->model->setAudioChannelFileName(QFileInfo());
0771 }
0772 
0773 void KisAnimTimelineFramesView::slotAudioVolumeChanged(int value)
0774 {
0775     m_d->model->setAudioVolume(qreal(value) / 100.0);
0776 }
0777 
0778 void KisAnimTimelineFramesView::slotScrollerStateChanged( QScroller::State state ) {
0779 
0780     if (state == QScroller::Dragging || state == QScroller::Scrolling ) {
0781         m_d->kineticScrollInfiniteFrameUpdater.start(16);
0782     } else {
0783         m_d->kineticScrollInfiniteFrameUpdater.stop();
0784     }
0785 
0786     KisKineticScroller::updateCursor(this, state);
0787 }
0788 
0789 void KisAnimTimelineFramesView::slotZoom(qreal zoom)
0790 {
0791     const int originalFirstColumn = m_d->horizontalRuler->estimateFirstVisibleColumn();
0792     if (m_d->horizontalRuler->setZoom(m_d->horizontalRuler->zoom() + zoom)) {
0793         const int newLastColumn = m_d->horizontalRuler->estimateFirstVisibleColumn();
0794         if (newLastColumn >= m_d->model->columnCount()) {
0795             slotUpdateInfiniteFramesCount();
0796         }
0797         viewport()->update();
0798         horizontalScrollBar()->setValue(scrollPositionFromColumn(originalFirstColumn));
0799     }
0800 }
0801 
0802 void KisAnimTimelineFramesView::slotUpdateDragInfiniteFramesCount() {
0803     if(m_d->dragInProgress ||
0804       (m_d->model->isScrubbing() && horizontalScrollBar()->sliderPosition() == horizontalScrollBar()->maximum()) ) {
0805         slotUpdateInfiniteFramesCount();
0806     }
0807 }
0808 
0809 void KisAnimTimelineFramesView::slotRealignScrollBars() {
0810     QScrollBar* hBar = horizontalScrollBar();
0811     QScrollBar* vBar = verticalScrollBar();
0812 
0813     QSize desiredScrollArea = QSize(width() - verticalHeader()->width(), height() - horizontalHeader()->height());
0814 
0815     // Compensate for corner gap...
0816     if (hBar->isVisible() && vBar->isVisible()) {
0817         desiredScrollArea -= QSize(vBar->width(), hBar->height());
0818     }
0819 
0820     hBar->parentWidget()->layout()->setAlignment(Qt::AlignRight);
0821     hBar->setMaximumWidth(desiredScrollArea.width());
0822     hBar->setMinimumWidth(desiredScrollArea.width());
0823 
0824 
0825     vBar->parentWidget()->layout()->setAlignment(Qt::AlignBottom);
0826     vBar->setMaximumHeight(desiredScrollArea.height());
0827     vBar->setMinimumHeight(desiredScrollArea.height());
0828 }
0829 
0830 void KisAnimTimelineFramesView::slotEnsureRowVisible(int row)
0831 {
0832     QModelIndex index = currentIndex();
0833     if (!index.isValid() || row < 0) return;
0834 
0835     index = m_d->model->index(row, index.column());
0836 
0837     // WORKAROUND BUG:437029
0838     // Delay's UI scrolling by 1/60 of a second to compensate for
0839     // inconsistent dummy indexing caused by a brief period where
0840     // two unpinned dummies exist on the timeline simultaneously.
0841     QTimer::singleShot(16, this, [this, index](){
0842         scrollTo(index);
0843     });
0844 }
0845 
0846 void KisAnimTimelineFramesView::calculateActiveLayerSelectedTimes(const QModelIndexList &selection)
0847 {
0848     QSet<int> activeLayerSelectedTimes;
0849     Q_FOREACH (const QModelIndex& index, selection) {
0850         if (index.data(KisAnimTimelineFramesModel::ActiveLayerRole).toBool()) {
0851             activeLayerSelectedTimes.insert(index.column());
0852         }
0853     }
0854 
0855     m_d->model->setActiveLayerSelectedTimes(activeLayerSelectedTimes);
0856 }
0857 
0858 bool KisAnimTimelineFramesView::viewportEvent(QEvent *event)
0859 {
0860     // Seems to have been copied over from KisResourceItemListView.
0861     // These tooltips currently give bogus info (empty thumbnail and resource location), so removed for now.
0862     // TODO: Implement meaningful tooltips if there's demand, probably including frame thumbnails.
0863 
0864     /*
0865     if (event->type() == QEvent::ToolTip && model()) {
0866         QHelpEvent *he = static_cast<QHelpEvent *>(event);
0867         QModelIndex index = model()->buddy(indexAt(he->pos()));
0868         if (index.isValid()) {
0869             QStyleOptionViewItem option = viewOptions();
0870             option.rect = visualRect(index);
0871             // The offset of the headers is needed to get the correct position inside the view.
0872             m_d->tip.showTip(this, he->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index);
0873             return true;
0874         }
0875     }
0876     */
0877 
0878     return QTableView::viewportEvent(event);
0879 }
0880 
0881 void KisAnimTimelineFramesView::mousePressEvent(QMouseEvent *event)
0882 {
0883     QPersistentModelIndex index = indexAt(event->pos());
0884 
0885     if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) {
0886 
0887         if (event->button() == Qt::RightButton) {
0888             // TODO: try calculate index under mouse cursor even when
0889             //       it is outside any visible row
0890 //            qreal staticPoint = index.isValid() ? index.column() : currentIndex().column();
0891 //            m_d->zoomDragButton->beginZoom(event->pos(), staticPoint);
0892         } else if (event->button() == Qt::LeftButton) {
0893             m_d->initialDragPanPos = event->pos();
0894             m_d->initialDragPanValue =
0895                     QPoint(horizontalScrollBar()->value(),
0896                            verticalScrollBar()->value());
0897         }
0898         event->accept();
0899 
0900     } else if (event->button() == Qt::RightButton) {
0901 
0902         int numSelectedItems = selectionModel()->selectedIndexes().size();
0903 
0904         if (index.isValid() &&
0905                 numSelectedItems <= 1 &&
0906                 m_d->model->data(index, KisAnimTimelineFramesModel::FrameEditableRole).toBool()) {
0907 
0908             model()->setData(index, true, KisAnimTimelineFramesModel::ActiveLayerRole);
0909             model()->setData(index, true, KisAnimTimelineFramesModel::ActiveFrameRole);
0910             model()->setData(index, QVariant(int(SEEK_FINALIZE | SEEK_PUSH_AUDIO)), KisAnimTimelineFramesModel::ScrubToRole);
0911             setCurrentIndex(index);
0912 
0913             if (model()->data(index, KisAnimTimelineFramesModel::FrameExistsRole).toBool() ||
0914                     model()->data(index, KisAnimTimelineFramesModel::SpecialKeyframeExists).toBool()) {
0915 
0916                 {
0917                     KisSignalsBlocker b(m_d->colorSelector->colorLabelSelector());
0918                     QVariant colorLabel = index.data(KisAnimTimelineFramesModel::FrameColorLabelIndexRole);
0919                     int labelIndex = colorLabel.isValid() ? colorLabel.toInt() : 0;
0920                     m_d->colorSelector->colorLabelSelector()->setCurrentIndex(labelIndex);
0921                 }
0922 
0923                 const bool hasClones = model()->data(index, KisAnimTimelineFramesModel::CloneCount).toInt() > 0;
0924 
0925                 QMenu menu;
0926                 createFrameEditingMenuActions(&menu, false, hasClones);
0927                 menu.addSeparator();
0928                 menu.addAction(m_d->colorSelectorAction);
0929                 menu.exec(event->globalPos());
0930 
0931             } else {
0932                 {
0933                     KisSignalsBlocker b(m_d->colorSelector->colorLabelSelector());
0934                     const int labelIndex = KisImageConfig(true).defaultFrameColorLabel();
0935                     m_d->colorSelector->colorLabelSelector()->setCurrentIndex(labelIndex);
0936                 }
0937 
0938                 QMenu menu;
0939                 createFrameEditingMenuActions(&menu, true, false);
0940                 menu.addSeparator();
0941                 menu.addAction(m_d->colorSelectorAction);
0942                 menu.exec(event->globalPos());
0943             }
0944         } else if (numSelectedItems > 1) {
0945             int labelIndex = -1;
0946             bool firstKeyframe = true;
0947             bool hasKeyframes = false;
0948             bool containsClones = false;
0949             Q_FOREACH(QModelIndex index, selectedIndexes()) {
0950                 hasKeyframes |= index.data(KisAnimTimelineFramesModel::FrameExistsRole).toBool();
0951                 containsClones |= (index.data(KisAnimTimelineFramesModel::CloneCount).toInt() > 0);
0952 
0953                 QVariant colorLabel = index.data(KisAnimTimelineFramesModel::FrameColorLabelIndexRole);
0954                 if (colorLabel.isValid()) {
0955                     if (firstKeyframe) {
0956                         labelIndex = colorLabel.toInt();
0957                     } else if (labelIndex != colorLabel.toInt()) {
0958                         // Mixed colors in selection
0959                         labelIndex = -1;
0960                     }
0961 
0962                     firstKeyframe = false;
0963                 }
0964 
0965                 if (!firstKeyframe
0966                     && hasKeyframes
0967                     && containsClones
0968                     && labelIndex == -1) {
0969                     break; // Break out early if we find all of the above.
0970                 }
0971             }
0972 
0973             if (hasKeyframes) {
0974                 KisSignalsBlocker b(m_d->multiframeColorSelector->colorLabelSelector());
0975                 m_d->multiframeColorSelector->colorLabelSelector()->setCurrentIndex(labelIndex);
0976             }
0977 
0978             QMenu menu;
0979             createFrameEditingMenuActions(&menu, false, containsClones);
0980             menu.addSeparator();
0981             KisActionManager::safePopulateMenu(&menu, "mirror_frames", m_d->actionMan);
0982             menu.addSeparator();
0983             menu.addAction(m_d->multiframeColorSelectorAction);
0984             menu.exec(event->globalPos());
0985         }
0986         
0987     } else if (event->button() == Qt::MiddleButton) {
0988         QModelIndex index = model()->buddy(indexAt(event->pos()));
0989         if (index.isValid()) {
0990             QStyleOptionViewItem option = viewOptions();
0991             option.rect = visualRect(index);
0992             // The offset of the headers is needed to get the correct position inside the view.
0993             m_d->tip.showTip(this, event->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index);
0994         }
0995         event->accept();
0996         
0997     } else {
0998         if (index.isValid()) {
0999             m_d->model->setLastClickedIndex(index);
1000         }
1001 
1002         m_d->lastPressedPosition =
1003                 QPoint(horizontalOffset(), verticalOffset()) + event->pos();
1004         m_d->lastPressedModifier = event->modifiers();
1005 
1006         m_d->initialDragPanPos = event->pos();
1007 
1008         QAbstractItemView::mousePressEvent(event);
1009     }
1010 }
1011 
1012 void KisAnimTimelineFramesView::mouseDoubleClickEvent(QMouseEvent *event) {
1013     QPersistentModelIndex index = indexAt(event->pos());
1014     
1015     if (index.isValid()) {
1016         if (event->modifiers() & Qt::AltModifier) {
1017             selectRow(index.row());
1018         } else {
1019             selectColumn(index.column());
1020         }
1021     }
1022     
1023     QAbstractItemView::mouseDoubleClickEvent(event);
1024 }
1025 
1026 void KisAnimTimelineFramesView::mouseMoveEvent(QMouseEvent *e)
1027 {
1028     // Custom keyframe dragging distance based on zoom level.
1029     if (state() == DraggingState &&
1030         (horizontalHeader()->defaultSectionSize() / 2) < QApplication::startDragDistance() ) {
1031 
1032         const QPoint dragVector = e->pos() - m_d->initialDragPanPos;
1033         if (dragVector.manhattanLength() >= (horizontalHeader()->defaultSectionSize() / 2)) {
1034             startDrag(model()->supportedDragActions());
1035             setState(NoState);
1036             stopAutoScroll();
1037         }
1038     }
1039 
1040     if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) {
1041 
1042         if (e->buttons() & Qt::RightButton) {
1043 
1044 //            m_d->zoomDragButton->continueZoom(e->pos());
1045         } else if (e->buttons() & Qt::LeftButton) {
1046 
1047             QPoint diff = e->pos() - m_d->initialDragPanPos;
1048             QPoint offset = QPoint(m_d->initialDragPanValue.x() - diff.x(),
1049                                    m_d->initialDragPanValue.y() - diff.y());
1050 
1051             const int height = m_d->layersHeader->defaultSectionSize();
1052 
1053             if (m_d->initialDragPanValue.x() - diff.x() > horizontalScrollBar()->maximum() || m_d->initialDragPanValue.x() - diff.x() > horizontalScrollBar()->minimum() ){
1054                 KisZoomableScrollBar* zoombar = static_cast<KisZoomableScrollBar*>(horizontalScrollBar());
1055                 zoombar->overscroll(-diff.x());
1056             }
1057 
1058             horizontalScrollBar()->setValue(offset.x());
1059             verticalScrollBar()->setValue(offset.y() / height);
1060         }
1061         e->accept();
1062     } else if (e->buttons() == Qt::MiddleButton) {
1063         QModelIndex index = model()->buddy(indexAt(e->pos()));
1064         if (index.isValid()) {
1065             QStyleOptionViewItem option = viewOptions();
1066             option.rect = visualRect(index);
1067             // The offset of the headers is needed to get the correct position inside the view.
1068             m_d->tip.showTip(this, e->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index);
1069         }
1070         e->accept();
1071     } else {
1072         m_d->model->setScrubState(true);
1073         QTableView::mouseMoveEvent(e);
1074     }
1075 }
1076 
1077 void KisAnimTimelineFramesView::mouseReleaseEvent(QMouseEvent *e)
1078 {
1079     if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) {
1080         e->accept();
1081     } else {
1082         m_d->model->setScrubState(false);
1083         QTableView::mouseReleaseEvent(e);
1084     }
1085 }
1086 
1087 void KisAnimTimelineFramesView::startDrag(Qt::DropActions supportedActions)
1088 {
1089     QModelIndexList indexes = selectionModel()->selectedIndexes();
1090 
1091     if (!indexes.isEmpty() && m_d->modifiersCatcher->modifierPressed("offset-frame")) {
1092         QVector<int> rows;
1093         int leftmostColumn = std::numeric_limits<int>::max();
1094 
1095         Q_FOREACH (const QModelIndex &index, indexes) {
1096             leftmostColumn = qMin(leftmostColumn, index.column());
1097             if (!rows.contains(index.row())) {
1098                 rows.append(index.row());
1099             }
1100         }
1101 
1102         const int lastColumn = m_d->model->columnCount() - 1;
1103 
1104         selectionModel()->clear();
1105         Q_FOREACH (const int row, rows) {
1106             QItemSelection sel(m_d->model->index(row, leftmostColumn), m_d->model->index(row, lastColumn));
1107             selectionModel()->select(sel, QItemSelectionModel::Select);
1108         }
1109 
1110         supportedActions = Qt::MoveAction;
1111 
1112         {
1113             QModelIndexList indexes = selectedIndexes();
1114             for(int i = indexes.count() - 1 ; i >= 0; --i) {
1115                 if (!isIndexDragEnabled(m_d->model, indexes.at(i)))
1116                     indexes.removeAt(i);
1117             }
1118 
1119             selectionModel()->clear();
1120 
1121             if (indexes.count() > 0) {
1122                 QMimeData *data = m_d->model->mimeData(indexes);
1123                 if (!data)
1124                     return;
1125                 QRect rect;
1126                 QPixmap pixmap = m_d->renderToPixmap(indexes, &rect);
1127                 rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
1128                 QDrag *drag = new QDrag(this);
1129                 drag->setPixmap(pixmap);
1130                 drag->setMimeData(data);
1131                 drag->setHotSpot(m_d->lastPressedPosition - rect.topLeft());
1132                 drag->exec(supportedActions, Qt::MoveAction);
1133                 setCurrentIndex(currentIndex());
1134             }
1135         }
1136     } else {
1137 
1138         /**
1139          * Workaround for Qt5's bug: if we start a dragging action right during
1140          * Shift-selection, Qt will get crazy. We cannot workaround it easily,
1141          * because we would need to fork mouseMoveEvent() for that (where the
1142          * decision about drag state is done). So we just abort dragging in that
1143          * case.
1144          *
1145          * BUG:373067
1146          */
1147         if (m_d->lastPressedModifier & Qt::ShiftModifier) {
1148             return;
1149         }
1150 
1151         /**
1152          * Workaround for Qt5's bugs:
1153          *
1154          * 1) Qt doesn't treat selection the selection on D&D
1155          *    correctly, so we save it in advance and restore
1156          *    afterwards.
1157          *
1158          * 2) There is a private variable in QAbstractItemView:
1159          *    QAbstractItemView::Private::currentSelectionStartIndex.
1160          *    It is initialized *only* when the setCurrentIndex() is called
1161          *    explicitly on the view object, not on the selection model.
1162          *    Therefore we should explicitly call setCurrentIndex() after
1163          *    D&D, even if it already has *correct* value!
1164          *
1165          * 2) We should also call selectionModel()->select()
1166          *    explicitly.  There are two reasons for it: 1) Qt doesn't
1167          *    maintain selection over D&D; 2) when reselecting single
1168          *    element after D&D, Qt goes crazy, because it tries to
1169          *    read *global* keyboard modifiers. Therefore if we are
1170          *    dragging with Shift or Ctrl pressed it'll get crazy. So
1171          *    just reset it explicitly.
1172          */
1173 
1174         QModelIndexList selectionBefore = selectionModel()->selectedIndexes();
1175         QModelIndex currentBefore = selectionModel()->currentIndex();
1176 
1177         // initialize a global status variable
1178         m_d->dragWasSuccessful = false;
1179         QAbstractItemView::startDrag(supportedActions);
1180 
1181         QModelIndex newCurrent;
1182         QPoint selectionOffset;
1183 
1184         if (m_d->dragWasSuccessful) {
1185             newCurrent = currentIndex();
1186             selectionOffset = QPoint(newCurrent.column() - currentBefore.column(),
1187                                      newCurrent.row() - currentBefore.row());
1188         } else {
1189             newCurrent = currentBefore;
1190             selectionOffset = QPoint();
1191         }
1192 
1193         setCurrentIndex(newCurrent);
1194         selectionModel()->clearSelection();
1195         Q_FOREACH (const QModelIndex &idx, selectionBefore) {
1196             QModelIndex newIndex =
1197                     model()->index(idx.row() + selectionOffset.y(),
1198                                    idx.column() + selectionOffset.x());
1199             selectionModel()->select(newIndex, QItemSelectionModel::Select);
1200         }
1201     }
1202 }
1203 
1204 void KisAnimTimelineFramesView::dragEnterEvent(QDragEnterEvent *event)
1205 {
1206     m_d->dragInProgress = true;
1207     m_d->model->setScrubState(true);
1208 
1209     QTableView::dragEnterEvent(event);
1210 }
1211 
1212 void KisAnimTimelineFramesView::dragMoveEvent(QDragMoveEvent *event)
1213 {
1214     m_d->dragInProgress = true;
1215     m_d->model->setScrubState(true);
1216 
1217     QAbstractItemView::dragMoveEvent(event);
1218 
1219     // Let's check for moving within a selection --
1220     // We want to override the built in qt behavior that
1221     // denies drag events when dragging within a selection...
1222     if (!event->isAccepted() && selectionModel()->isSelected(indexAt(event->pos()))) {
1223         event->setAccepted(true);
1224     }
1225 
1226     if (event->isAccepted()) {
1227         QModelIndex index = indexAt(event->pos());
1228 
1229         if (!m_d->model->canDropFrameData(event->mimeData(), index)) {
1230             event->ignore();
1231         } else {
1232             selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
1233         }
1234     }
1235 }
1236 
1237 void KisAnimTimelineFramesView::dragLeaveEvent(QDragLeaveEvent *event)
1238 {
1239     m_d->dragInProgress = false;
1240     m_d->model->setScrubState(false);
1241 
1242     QAbstractItemView::dragLeaveEvent(event);
1243 }
1244 
1245 void KisAnimTimelineFramesView::dropEvent(QDropEvent *event)
1246 {
1247     m_d->dragInProgress = false;
1248     m_d->model->setScrubState(false);
1249 
1250     if (event->keyboardModifiers() & Qt::ControlModifier) {
1251         event->setDropAction(Qt::CopyAction);
1252     } else if (event->keyboardModifiers() & Qt::AltModifier) {
1253         event->setDropAction(Qt::LinkAction);
1254     }
1255 
1256     QAbstractItemView::dropEvent(event);
1257 
1258     // Override drop event to accept drops within selected range.0
1259     QModelIndex index = indexAt(event->pos());
1260     if (!event->isAccepted() &&  selectionModel()->isSelected(index)) {
1261         event->setAccepted(true);
1262         const Qt::DropAction action = event->dropAction();
1263         const int row = event->pos().y();
1264         const int column = event->pos().x();
1265         if (m_d->model->dropMimeData(event->mimeData(), action, row, column, index)) {
1266             event->acceptProposedAction();
1267         }
1268     }
1269 
1270     m_d->dragWasSuccessful = event->isAccepted();
1271 }
1272 
1273 void KisAnimTimelineFramesView::wheelEvent(QWheelEvent *e)
1274 {
1275     const int scrollDirection = e->delta() > 0 ? 1 : -1;
1276     bool mouseOverLayerPanel = verticalHeader()->geometry().contains(verticalHeader()->mapFromGlobal(e->globalPos()));
1277 
1278     if (mouseOverLayerPanel) {
1279         QTableView::wheelEvent(e);
1280     } else { // Mouse is over frames table view...
1281         QModelIndex index = currentIndex();
1282         int column= -1;
1283 
1284         if (index.isValid()) {
1285             column = index.column() + scrollDirection;
1286         }
1287 
1288         if (column >= 0 && !m_d->dragInProgress) {
1289             slotUpdateInfiniteFramesCount();
1290             setCurrentIndex(m_d->model->index(index.row(), column));
1291         }
1292     }
1293 }
1294 
1295 void KisAnimTimelineFramesView::resizeEvent(QResizeEvent *event)
1296 {
1297     Q_UNUSED(event);
1298 
1299     updateGeometries();
1300     slotUpdateInfiniteFramesCount();
1301 }
1302 
1303 void KisAnimTimelineFramesView::rowsInserted(const QModelIndex& parent, int start, int end)
1304 {
1305     QTableView::rowsInserted(parent, start, end);
1306 }
1307 
1308 void KisAnimTimelineFramesView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
1309 {
1310     QTableView::currentChanged(current, previous);
1311 
1312     if (previous.column() != current.column()) {
1313         m_d->model->setData(previous, false, KisAnimTimelineFramesModel::ActiveFrameRole);
1314         m_d->model->setData(current, true, KisAnimTimelineFramesModel::ActiveFrameRole);
1315         if ( current.column() != m_d->model->currentTime() ) {
1316             m_d->model->setData(current, QVariant(int(SEEK_FINALIZE | SEEK_PUSH_AUDIO)), KisAnimTimelineFramesModel::ScrubToRole);
1317         }
1318     }
1319 }
1320 
1321 QItemSelectionModel::SelectionFlags KisAnimTimelineFramesView::selectionCommand(const QModelIndex &index,
1322                                                                          const QEvent *event) const
1323 {
1324     // WARNING: Copy-pasted from KisNodeView! Please keep in sync!
1325 
1326     /**
1327      * Qt has a bug: when we Ctrl+click on an item, the item's
1328      * selections gets toggled on mouse *press*, whereas usually it is
1329      * done on mouse *release*.  Therefore the user cannot do a
1330      * Ctrl+D&D with the default configuration. This code fixes the
1331      * problem by manually returning QItemSelectionModel::NoUpdate
1332      * flag when the user clicks on an item and returning
1333      * QItemSelectionModel::Toggle on release.
1334      */
1335 
1336     if (event &&
1337             (event->type() == QEvent::MouseButtonPress ||
1338              event->type() == QEvent::MouseButtonRelease) &&
1339             index.isValid()) {
1340 
1341         const QMouseEvent *mevent = static_cast<const QMouseEvent*>(event);
1342 
1343         if (mevent->button() == Qt::RightButton &&
1344                 selectionModel()->selectedIndexes().contains(index)) {
1345 
1346             // Allow calling context menu for multiple layers
1347             return QItemSelectionModel::NoUpdate;
1348         }
1349 
1350         if (event->type() == QEvent::MouseButtonPress &&
1351                 (mevent->modifiers() & Qt::ControlModifier)) {
1352 
1353             return QItemSelectionModel::NoUpdate;
1354         }
1355 
1356         if (event->type() == QEvent::MouseButtonRelease &&
1357                 (mevent->modifiers() & Qt::ControlModifier)) {
1358 
1359             return QItemSelectionModel::Toggle;
1360         }
1361     }
1362 
1363     return QAbstractItemView::selectionCommand(index, event);
1364 }
1365 
1366 void KisAnimTimelineFramesView::setFramesPerSecond(int fps)
1367 {
1368     m_d->fps = fps;
1369     m_d->horizontalRuler->setFramePerSecond(fps);
1370 }
1371 
1372 QModelIndexList KisAnimTimelineFramesView::calculateSelectionSpan(bool entireColumn, bool editableOnly) const
1373 {
1374     QModelIndexList indexes;
1375 
1376     if (entireColumn) {
1377         QSet<int> rows;
1378         int minColumn = 0;
1379         int maxColumn = 0;
1380 
1381         calculateSelectionMetrics(minColumn, maxColumn, rows);
1382 
1383         rows.clear();
1384         for (int i = 0; i < m_d->model->rowCount(); i++) {
1385             if (editableOnly &&
1386                 !m_d->model->data(m_d->model->index(i, minColumn), KisAnimTimelineFramesModel::FrameEditableRole).toBool()) continue;
1387 
1388             for (int column = minColumn; column <= maxColumn; column++) {
1389                 indexes << m_d->model->index(i, column);
1390             }
1391         }
1392     } else {
1393         Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) {
1394             if (!editableOnly || m_d->model->data(index, KisAnimTimelineFramesModel::FrameEditableRole).toBool()) {
1395                 indexes << index;
1396             }
1397         }
1398     }
1399 
1400     return indexes;
1401 }
1402 
1403 void KisAnimTimelineFramesView::calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet<int> &rows) const
1404 {
1405     minColumn = std::numeric_limits<int>::max();
1406     maxColumn = std::numeric_limits<int>::min();
1407 
1408     Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) {
1409         if (!m_d->model->data(index, KisAnimTimelineFramesModel::FrameEditableRole).toBool()) continue;
1410 
1411         rows.insert(index.row());
1412         minColumn = qMin(minColumn, index.column());
1413         maxColumn = qMax(maxColumn, index.column());
1414     }
1415 }
1416 
1417 void KisAnimTimelineFramesView::insertKeyframes(int count, int timing, TimelineDirection direction, bool entireColumn)
1418 {
1419     QSet<int> rows;
1420     int minColumn = 0, maxColumn = 0;
1421 
1422     calculateSelectionMetrics(minColumn, maxColumn, rows);
1423 
1424     if (count <= 0) { //Negative count? Use number of selected frames.
1425         count = qMax(1, maxColumn - minColumn + 1);
1426     }
1427 
1428     const int insertionColumn =
1429         direction == TimelineDirection::RIGHT ?
1430         maxColumn + 1 : minColumn;
1431 
1432     if (entireColumn) {
1433         rows.clear();
1434         for (int i = 0; i < m_d->model->rowCount(); i++) {
1435             if (!m_d->model->data(m_d->model->index(i, insertionColumn), KisAnimTimelineFramesModel::FrameEditableRole).toBool()) continue;
1436             rows.insert(i);
1437         }
1438     }
1439 
1440     if (!rows.isEmpty()) {
1441 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
1442         m_d->model->insertFrames(insertionColumn, QList<int>(rows.begin(), rows.end()), count, timing);
1443 #else
1444         m_d->model->insertFrames(insertionColumn, QList<int>::fromSet(rows), count, timing);
1445 #endif
1446     }
1447 }
1448 
1449 void KisAnimTimelineFramesView::insertMultipleKeyframes(bool entireColumn)
1450 {
1451     int count, timing;
1452     TimelineDirection direction;
1453 
1454     if (m_d->insertKeyframeDialog->promptUserSettings(count, timing, direction)) {
1455         insertKeyframes(count, timing, direction, entireColumn);
1456     }
1457 }
1458 
1459 void KisAnimTimelineFramesView::insertOrRemoveHoldFrames(int count, bool entireColumn)
1460 {
1461     QModelIndexList indexes;
1462 
1463     // Populate indices..
1464     if (!entireColumn) {
1465         Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) {
1466             if (m_d->model->data(index, KisAnimTimelineFramesModel::FrameEditableRole).toBool()) {
1467                 indexes << index;
1468             }
1469         }
1470     } else {
1471         const int column = selectionModel()->currentIndex().column();
1472 
1473         for (int i = 0; i < m_d->model->rowCount(); i++) {
1474             const QModelIndex index = m_d->model->index(i, column);
1475             if (m_d->model->data(index, KisAnimTimelineFramesModel::FrameEditableRole).toBool()) {
1476                 indexes << index;
1477             }
1478         }
1479     }
1480     
1481     if (!indexes.isEmpty()) {
1482         m_d->model->insertHoldFrames(indexes, count);
1483 
1484         // Fan selection based on insertion or deletion.
1485         // This should allow better UI/UX for insertion of keyframes or hold frames.
1486         fanSelectedFrames(indexes, count);
1487 
1488         // bulk adding frames can add too many
1489         // trim timeline to clean up extra frames that might have been added
1490         slotUpdateInfiniteFramesCount();
1491     }
1492 }
1493 
1494 void KisAnimTimelineFramesView::insertOrRemoveMultipleHoldFrames(bool insertion, bool entireColumn)
1495 {
1496     bool ok = false;
1497     const int count = QInputDialog::getInt(this,
1498                                            i18nc("@title:window", "Insert or Remove Hold Frames"),
1499                                            i18nc("@label:spinbox", "Enter number of frames"),
1500                                            insertion ?
1501                                                m_d->insertKeyframeDialog->defaultTimingOfAddedFrames() :
1502                                                m_d->insertKeyframeDialog->defaultNumberOfHoldFramesToRemove(),
1503                                            1, 10000, 1, &ok);
1504 
1505     if (ok) {
1506         if (insertion) {
1507             m_d->insertKeyframeDialog->setDefaultTimingOfAddedFrames(count);
1508             insertOrRemoveHoldFrames(count, entireColumn);
1509         } else {
1510             m_d->insertKeyframeDialog->setDefaultNumberOfHoldFramesToRemove(count);
1511             insertOrRemoveHoldFrames(-count, entireColumn);
1512         }
1513 
1514     }
1515 }
1516 
1517 void KisAnimTimelineFramesView::fanSelectedFrames(const QModelIndexList &selection, int count, bool ignoreKeyless) {
1518     QMap<int, QList<int>> indexMap;
1519 
1520     QList<QModelIndex> selectedIndices = selection;
1521 
1522     foreach (const QModelIndex &index, selectedIndices) {
1523         if (!indexMap.contains(index.row())) {
1524             indexMap.insert(index.row(), QList<int>());
1525         }
1526 
1527         if (m_d->model->data(index, KisAnimTimelineFramesModel::FrameExistsRole).value<bool>() || !ignoreKeyless) {
1528             indexMap[index.row()] << index.column();
1529         }
1530     }
1531 
1532     KisSignalsBlocker blockSig(selectionModel());
1533     selectionModel()->clearSelection();
1534     foreach (const int &layer, indexMap.keys()) {
1535         QList<int>::const_iterator it;
1536         int progressIndex = 0;
1537 
1538         std::sort(indexMap[layer].begin(), indexMap[layer].end());
1539         for (it = indexMap[layer].constBegin(); it != indexMap[layer].constEnd(); it++) {
1540             const int offsetColumn = *it + (progressIndex * count);
1541             selectionModel()->select(model()->index(layer, offsetColumn), QItemSelectionModel::Select);
1542             progressIndex++;
1543         }
1544     }
1545 }
1546 
1547 void KisAnimTimelineFramesView::cutCopyImpl(bool entireColumn, bool copy)
1548 {
1549     const QModelIndexList selectedIndices = calculateSelectionSpan(entireColumn, !copy);
1550     if (selectedIndices.isEmpty()) return;
1551 
1552     int minColumn = std::numeric_limits<int>::max();
1553     int minRow = std::numeric_limits<int>::max();
1554     Q_FOREACH (const QModelIndex &index, selectedIndices) {
1555         minRow = qMin(minRow, index.row());
1556         minColumn = qMin(minColumn, index.column());
1557     }
1558 
1559     const QModelIndex baseIndex = m_d->model->index(minRow, minColumn);
1560     QMimeData *data = m_d->model->mimeDataExtended(selectedIndices,
1561                                                    baseIndex,
1562                                                    copy ?
1563                                                        KisAnimTimelineFramesModel::CopyFramesPolicy :
1564                                                        KisAnimTimelineFramesModel::MoveFramesPolicy);
1565 
1566     if (data) {
1567         QClipboard *cb = QApplication::clipboard();
1568         cb->setMimeData(data);
1569     }
1570 }
1571 
1572 void KisAnimTimelineFramesView::clone(bool entireColumn)
1573 {
1574     const QModelIndexList selectedIndices = calculateSelectionSpan(entireColumn, false);
1575     if (selectedIndices.isEmpty()) return;
1576 
1577     int minColumn = std::numeric_limits<int>::max();
1578     int minRow = std::numeric_limits<int>::max();
1579     Q_FOREACH (const QModelIndex &index, selectedIndices) {
1580         minRow = qMin(minRow, index.row());
1581         minColumn = qMin(minColumn, index.column());
1582     }
1583 
1584     const QModelIndex baseIndex = m_d->model->index(minRow, minColumn);
1585     QMimeData *data = m_d->model->mimeDataExtended(selectedIndices,
1586                                                    baseIndex,
1587                                                    KisAnimTimelineFramesModel::CloneFramesPolicy);
1588 
1589     if (data) {
1590         QClipboard *cb = QApplication::clipboard();
1591         cb->setMimeData(data);
1592     }
1593 }
1594 
1595 void KisAnimTimelineFramesView::createFrameEditingMenuActions(QMenu *menu, bool emptyFrame, bool cloneFrameSelected)
1596 {
1597     slotUpdateFrameActions();
1598 
1599     // calculate if selection range is set. This will determine if the update playback range is available
1600     QSet<int> rows;
1601     int minColumn = 0;
1602     int maxColumn = 0;
1603     calculateSelectionMetrics(minColumn, maxColumn, rows);
1604     bool selectionExists = minColumn != maxColumn;
1605 
1606     menu->addSection(i18n("Edit Frames:"));
1607     menu->addSeparator();
1608 
1609     if (selectionExists) {
1610         KisActionManager::safePopulateMenu(menu, "update_playback_range", m_d->actionMan);
1611     } else {
1612         KisActionManager::safePopulateMenu(menu, "set_start_time", m_d->actionMan);
1613         KisActionManager::safePopulateMenu(menu, "set_end_time", m_d->actionMan);
1614     }
1615 
1616     menu->addSeparator();
1617 
1618     if (!emptyFrame) {
1619         KisActionManager::safePopulateMenu(menu, "cut_frames", m_d->actionMan);
1620         KisActionManager::safePopulateMenu(menu, "copy_frames", m_d->actionMan);
1621         KisActionManager::safePopulateMenu(menu, "copy_frames_as_clones", m_d->actionMan);
1622     }
1623 
1624     KisActionManager::safePopulateMenu(menu, "paste_frames", m_d->actionMan);
1625 
1626     if (!emptyFrame && cloneFrameSelected) {
1627         KisActionManager::safePopulateMenu(menu, "make_clones_unique", m_d->actionMan);
1628     }
1629 
1630     menu->addSeparator();
1631 
1632     {   //Frames submenu.
1633         QMenu *frames = menu->addMenu(i18nc("@item:inmenu", "Keyframes"));
1634         KisActionManager::safePopulateMenu(frames, "insert_keyframe_left", m_d->actionMan);
1635         KisActionManager::safePopulateMenu(frames, "insert_keyframe_right", m_d->actionMan);
1636         frames->addSeparator();
1637         KisActionManager::safePopulateMenu(frames, "insert_multiple_keyframes", m_d->actionMan);
1638     }
1639 
1640     {   //Holds submenu.
1641         QMenu *hold = menu->addMenu(i18nc("@item:inmenu", "Hold Frames"));
1642         KisActionManager::safePopulateMenu(hold, "insert_hold_frame", m_d->actionMan);
1643         KisActionManager::safePopulateMenu(hold, "remove_hold_frame", m_d->actionMan);
1644         hold->addSeparator();
1645         KisActionManager::safePopulateMenu(hold, "insert_multiple_hold_frames", m_d->actionMan);
1646         KisActionManager::safePopulateMenu(hold, "remove_multiple_hold_frames", m_d->actionMan);
1647     }
1648 
1649     menu->addSeparator();
1650 
1651     if (!emptyFrame) {
1652         KisActionManager::safePopulateMenu(menu, "remove_frames", m_d->actionMan);
1653     }
1654     KisActionManager::safePopulateMenu(menu, "remove_frames_and_pull", m_d->actionMan);
1655 
1656     menu->addSeparator();
1657 
1658     if (emptyFrame) {
1659         KisActionManager::safePopulateMenu(menu, "add_blank_frame", m_d->actionMan);
1660         KisActionManager::safePopulateMenu(menu, "add_duplicate_frame", m_d->actionMan);
1661         menu->addSeparator();
1662     }
1663 }
1664 
1665 int KisAnimTimelineFramesView::scrollPositionFromColumn(int column) {
1666     const int sectionWidth = m_d->horizontalRuler->defaultSectionSize();
1667     return sectionWidth * column;
1668 }
1669 
1670 QStyleOptionViewItem KisAnimTimelineFramesView::Private::viewOptionsV4() const
1671 {
1672     QStyleOptionViewItem option = q->viewOptions();
1673     option.locale = q->locale();
1674     option.locale.setNumberOptions(QLocale::OmitGroupSeparator);
1675     option.widget = q;
1676     return option;
1677 }
1678 
1679 QItemViewPaintPairs KisAnimTimelineFramesView::Private::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const
1680 {
1681     Q_ASSERT(r);
1682     QRect &rect = *r;
1683     const QRect viewportRect = q->viewport()->rect();
1684     QItemViewPaintPairs ret;
1685     for (int i = 0; i < indexes.count(); ++i) {
1686         const QModelIndex &index = indexes.at(i);
1687         const QRect current = q->visualRect(index);
1688         if (current.intersects(viewportRect)) {
1689             ret += qMakePair(current, index);
1690             rect |= current;
1691         }
1692     }
1693     rect &= viewportRect;
1694     return ret;
1695 }
1696 
1697 QPixmap KisAnimTimelineFramesView::Private::renderToPixmap(const QModelIndexList &indexes, QRect *r) const
1698 {
1699     Q_ASSERT(r);
1700     QItemViewPaintPairs paintPairs = draggablePaintPairs(indexes, r);
1701     if (paintPairs.isEmpty())
1702         return QPixmap();
1703     QPixmap pixmap(r->size());
1704     pixmap.fill(Qt::transparent);
1705     QPainter painter(&pixmap);
1706     QStyleOptionViewItem option = viewOptionsV4();
1707     option.state |= QStyle::State_Selected;
1708     for (int j = 0; j < paintPairs.count(); ++j) {
1709         option.rect = paintPairs.at(j).first.translated(-r->topLeft());
1710         const QModelIndex &current = paintPairs.at(j).second;
1711         //adjustViewOptionsForIndex(&option, current);
1712 
1713         q->itemDelegate(current)->paint(&painter, option, current);
1714     }
1715     return pixmap;
1716 }
1717 
1718 void resizeToMinimalSize(QAbstractButton *w, int minimalSize)
1719 {
1720     QSize buttonSize = w->sizeHint();
1721     if (buttonSize.height() > minimalSize) {
1722         buttonSize = QSize(minimalSize, minimalSize);
1723     }
1724     w->resize(buttonSize);
1725 }
1726 
1727 inline bool isIndexDragEnabled(QAbstractItemModel *model, const QModelIndex &index)
1728 {
1729     return (model->flags(index) & Qt::ItemIsDragEnabled);
1730 }