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 ¤t, 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 ¤t = 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 }