File indexing completed on 2024-05-26 04:32:27
0001 /* 0002 * SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com> 0003 * SPDX-FileCopyrightText: 2021 Eoin O'Neil <eoinoneill1991@gmail.com> 0004 * SPDX-FileCopyrightText: 2021 Emmet O'Neill <emmetoneill.pdx@gmail.com> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "KisAnimTimelineTimeHeader.h" 0010 0011 #include <limits> 0012 0013 #include <QMenu> 0014 #include <QAction> 0015 #include <QPainter> 0016 #include <QPaintEvent> 0017 #include <KisPlaybackEngine.h> 0018 0019 #include <klocalizedstring.h> 0020 0021 #include "KisTimeBasedItemModel.h" 0022 #include "KisAnimTimelineColors.h" 0023 #include "kis_action.h" 0024 #include "kis_signal_compressor_with_param.h" 0025 #include "kis_config.h" 0026 0027 #include "kis_debug.h" 0028 0029 struct KisAnimTimelineTimeHeader::Private 0030 { 0031 Private() 0032 : model(nullptr) 0033 , actionMan(nullptr) 0034 , fps(12) 0035 , lastPressSectionIndex(-1) 0036 { 0037 // Compressed configuration writing.. 0038 const int compressorDelayMS = 100; 0039 zoomSaveCompressor.reset( 0040 new KisSignalCompressorWithParam<qreal>(compressorDelayMS, 0041 [](qreal zoomValue){ 0042 KisConfig cfg(false); 0043 cfg.setTimelineZoom(zoomValue); 0044 }, 0045 KisSignalCompressor::POSTPONE) 0046 ); 0047 } 0048 0049 KisTimeBasedItemModel* model; 0050 KisActionManager* actionMan; 0051 QScopedPointer<KisSignalCompressorWithParam<qreal>> zoomSaveCompressor; 0052 0053 int fps; 0054 int lastPressSectionIndex; 0055 0056 qreal offset = 0.0f; 0057 const int minSectionSize = 4; 0058 const int maxSectionSize = 72; 0059 const int unitSectionSize = 18; 0060 qreal remainder = 0.0f; 0061 0062 int calcSpanWidth(const int sectionWidth); 0063 QModelIndexList prepareFramesSlab(int startCol, int endCol); 0064 }; 0065 0066 KisAnimTimelineTimeHeader::KisAnimTimelineTimeHeader(QWidget *parent) 0067 : QHeaderView(Qt::Horizontal, parent) 0068 , m_d(new Private) 0069 { 0070 setSectionResizeMode(QHeaderView::Fixed); 0071 setDefaultSectionSize(18); 0072 setMinimumSectionSize(8); 0073 } 0074 0075 KisAnimTimelineTimeHeader::~KisAnimTimelineTimeHeader() 0076 { 0077 } 0078 0079 void KisAnimTimelineTimeHeader::setPixelOffset(qreal offset) 0080 { 0081 m_d->offset = qMax(offset, qreal(0.f)); 0082 setOffset(m_d->offset); 0083 viewport()->update(); 0084 } 0085 0086 void KisAnimTimelineTimeHeader::setActionManager(KisActionManager *actionManager) 0087 { 0088 m_d->actionMan = actionManager; 0089 0090 disconnect(this, &KisAnimTimelineTimeHeader::sigZoomChanged, this, &KisAnimTimelineTimeHeader::slotSaveThrottle); 0091 0092 if (actionManager) { 0093 KisAction *action; 0094 0095 action = actionManager->createAction("insert_column_left"); 0096 connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnLeft())); 0097 0098 action = actionManager->createAction("insert_column_right"); 0099 connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnRight())); 0100 0101 action = actionManager->createAction("insert_multiple_columns"); 0102 connect(action, SIGNAL(triggered()), SIGNAL(sigInsertMultipleColumns())); 0103 0104 action = actionManager->createAction("remove_columns_and_pull"); 0105 connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveColumnsAndShift())); 0106 0107 action = actionManager->createAction("remove_columns"); 0108 connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveColumns())); 0109 0110 action = actionManager->createAction("insert_hold_column"); 0111 connect(action, SIGNAL(triggered()), SIGNAL(sigInsertHoldColumns())); 0112 0113 action = actionManager->createAction("insert_multiple_hold_columns"); 0114 connect(action, SIGNAL(triggered()), SIGNAL(sigInsertHoldColumnsCustom())); 0115 0116 action = actionManager->createAction("remove_hold_column"); 0117 connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveHoldColumns())); 0118 0119 action = actionManager->createAction("remove_multiple_hold_columns"); 0120 connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveHoldColumnsCustom())); 0121 0122 action = actionManager->createAction("mirror_columns"); 0123 connect(action, SIGNAL(triggered()), SIGNAL(sigMirrorColumns())); 0124 0125 action = actionManager->createAction("clear_animation_cache"); 0126 connect(action, SIGNAL(triggered()), SIGNAL(sigClearCache())); 0127 0128 action = actionManager->createAction("copy_columns_to_clipboard"); 0129 connect(action, SIGNAL(triggered()), SIGNAL(sigCopyColumns())); 0130 0131 action = actionManager->createAction("cut_columns_to_clipboard"); 0132 connect(action, SIGNAL(triggered()), SIGNAL(sigCutColumns())); 0133 0134 action = actionManager->createAction("paste_columns_from_clipboard"); 0135 connect(action, SIGNAL(triggered()), SIGNAL(sigPasteColumns())); 0136 0137 KisConfig cfg(true); 0138 setZoom(cfg.timelineZoom()); 0139 connect(this, &KisAnimTimelineTimeHeader::sigZoomChanged, this, &KisAnimTimelineTimeHeader::slotSaveThrottle); 0140 } 0141 } 0142 0143 0144 void KisAnimTimelineTimeHeader::paintEvent(QPaintEvent *e) 0145 { 0146 QHeaderView::paintEvent(e); 0147 0148 // Copied from Qt 4.8... 0149 0150 if (count() == 0) 0151 return; 0152 0153 QPainter painter(viewport()); 0154 const QPoint offset = dirtyRegionOffset(); 0155 QRect translatedEventRect = e->rect(); 0156 translatedEventRect.translate(offset); 0157 0158 int start = -1; 0159 int end = -1; 0160 if (orientation() == Qt::Horizontal) { 0161 start = visualIndexAt(translatedEventRect.left()); 0162 end = visualIndexAt(translatedEventRect.right()); 0163 } else { 0164 start = visualIndexAt(translatedEventRect.top()); 0165 end = visualIndexAt(translatedEventRect.bottom()); 0166 } 0167 0168 const bool reverseImpl = orientation() == Qt::Horizontal && isRightToLeft(); 0169 0170 if (reverseImpl) { 0171 start = (start == -1 ? count() - 1 : start); 0172 end = (end == -1 ? 0 : end); 0173 } else { 0174 start = (start == -1 ? 0 : start); 0175 end = (end == -1 ? count() - 1 : end); 0176 } 0177 0178 int tmp = start; 0179 start = qMin(start, end); 0180 end = qMax(tmp, end); 0181 0182 /////////////////////////////////////////////////// 0183 /// Krita specific code. We should update in spans! 0184 0185 const int spanStart = start - start % m_d->fps; 0186 const int spanEnd = end - end % m_d->fps + m_d->fps - 1; 0187 0188 start = spanStart; 0189 end = qMin(count() - 1, spanEnd); 0190 0191 /// End of Krita specific code 0192 /////////////////////////////////////////////////// 0193 0194 QRect currentSectionRect; 0195 int logical; 0196 const int width = viewport()->width(); 0197 const int height = viewport()->height(); 0198 0199 for (int i = start; i <= end; ++i) { 0200 // DK: cannot copy-paste easily... 0201 // if (d->isVisualIndexHidden(i)) 0202 // continue; 0203 painter.save(); 0204 logical = logicalIndex(i); 0205 if (orientation() == Qt::Horizontal) { 0206 currentSectionRect.setRect(sectionViewportPosition(logical), 0, sectionSize(logical), height); 0207 } else { 0208 currentSectionRect.setRect(0, sectionViewportPosition(logical), width, sectionSize(logical)); 0209 } 0210 currentSectionRect.translate(offset); 0211 0212 QVariant variant = model()->headerData(logical, orientation(), 0213 Qt::FontRole); 0214 if (variant.isValid() && variant.canConvert<QFont>()) { 0215 QFont sectionFont = qvariant_cast<QFont>(variant); 0216 painter.setFont(sectionFont); 0217 } 0218 paintSection1(&painter, currentSectionRect, logical); 0219 painter.restore(); 0220 } 0221 } 0222 0223 void KisAnimTimelineTimeHeader::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const 0224 { 0225 // Base paint event should paint nothing in the sections area 0226 0227 Q_UNUSED(painter); 0228 Q_UNUSED(rect); 0229 Q_UNUSED(logicalIndex); 0230 } 0231 0232 void KisAnimTimelineTimeHeader::paintSpan(QPainter *painter, int userFrameId, 0233 const QRect &spanRect, 0234 bool isIntegralLine, 0235 bool isPrevIntegralLine, 0236 QStyle *style, 0237 const QPalette &palette, 0238 const QPen &gridPen) const 0239 { 0240 painter->fillRect(spanRect, palette.brush(QPalette::Button)); 0241 0242 int safeRight = spanRect.right(); 0243 0244 QPen oldPen = painter->pen(); 0245 painter->setPen(gridPen); 0246 0247 int adjustedTop = spanRect.top() + (!isIntegralLine ? spanRect.height() / 2 : 0); 0248 painter->drawLine(safeRight, adjustedTop, safeRight, spanRect.bottom()); 0249 0250 if (isPrevIntegralLine) { 0251 painter->drawLine(spanRect.left() + 1, spanRect.top(), spanRect.left() + 1, spanRect.bottom()); 0252 } 0253 0254 painter->setPen(oldPen); 0255 0256 QString frameIdText = QString::number(userFrameId); 0257 QRect textRect(spanRect.topLeft() + QPoint(2, 0), QSize(spanRect.width() - 2, spanRect.height())); 0258 0259 QStyleOptionHeader opt; 0260 initStyleOption(&opt); 0261 0262 QStyle::State state = QStyle::State_None; 0263 if (isEnabled()) 0264 state |= QStyle::State_Enabled; 0265 if (window()->isActiveWindow()) 0266 state |= QStyle::State_Active; 0267 opt.state |= state; 0268 opt.selectedPosition = QStyleOptionHeader::NotAdjacent; 0269 0270 opt.textAlignment = Qt::AlignLeft | Qt::AlignTop; 0271 opt.rect = textRect; 0272 opt.text = frameIdText; 0273 style->drawControl(QStyle::CE_HeaderLabel, &opt, painter, this); 0274 } 0275 0276 void KisAnimTimelineTimeHeader::slotSaveThrottle(qreal value) 0277 { 0278 m_d->zoomSaveCompressor->start(value); 0279 } 0280 0281 int KisAnimTimelineTimeHeader::Private::calcSpanWidth(const int sectionWidth) { 0282 const int minWidth = 36; 0283 0284 int spanWidth = this->fps; 0285 0286 while (spanWidth * sectionWidth < minWidth) { 0287 spanWidth *= 2; 0288 } 0289 0290 bool splitHappened = false; 0291 0292 do { 0293 splitHappened = false; 0294 0295 if (!(spanWidth & 0x1) && 0296 spanWidth * sectionWidth / 2 > minWidth) { 0297 0298 spanWidth /= 2; 0299 splitHappened = true; 0300 0301 } else if (!(spanWidth % 3) && 0302 spanWidth * sectionWidth / 3 > minWidth) { 0303 0304 spanWidth /= 3; 0305 splitHappened = true; 0306 0307 } else if (!(spanWidth % 5) && 0308 spanWidth * sectionWidth / 5 > minWidth) { 0309 0310 spanWidth /= 5; 0311 splitHappened = true; 0312 } 0313 0314 } while (splitHappened); 0315 0316 0317 if (sectionWidth > minWidth) { 0318 spanWidth = 1; 0319 } 0320 0321 return spanWidth; 0322 } 0323 0324 void KisAnimTimelineTimeHeader::paintSection1(QPainter *painter, const QRect &rect, int logicalIndex) const 0325 { 0326 0327 if (!rect.isValid()) 0328 return; 0329 0330 QFontMetrics metrics(this->font()); 0331 const int textHeight = metrics.height(); 0332 0333 QPoint p1 = rect.topLeft() + QPoint(0, textHeight); 0334 QPoint p2 = rect.topRight() + QPoint(0, textHeight); 0335 0336 QRect frameRect = QRect(p1, QSize(rect.width(), rect.height() - textHeight)); 0337 0338 const int width = rect.width(); 0339 0340 int spanWidth = m_d->calcSpanWidth(width); 0341 0342 const int internalIndex = logicalIndex % spanWidth; 0343 const int userFrameId = logicalIndex; 0344 0345 const int spanEnd = qMin(count(), logicalIndex + spanWidth); 0346 QRect spanRect(rect.topLeft(), QSize(width * (spanEnd - logicalIndex), textHeight)); 0347 0348 QStyleOptionViewItem option = viewOptions(); 0349 const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this); 0350 const QColor gridColor = static_cast<QRgb>(gridHint); 0351 const QPen gridPen = QPen(gridColor); 0352 0353 if (!internalIndex) { 0354 bool isIntegralLine = (logicalIndex + spanWidth) % m_d->fps == 0; 0355 bool isPrevIntegralLine = logicalIndex % m_d->fps == 0; 0356 paintSpan(painter, userFrameId, spanRect, isIntegralLine, isPrevIntegralLine, style(), palette(), gridPen); 0357 } 0358 0359 { 0360 QBrush fillColor = KisAnimTimelineColors::instance()->headerEmpty(); 0361 0362 QVariant activeValue = model()->headerData(logicalIndex, orientation(), 0363 KisTimeBasedItemModel::ActiveFrameRole); 0364 0365 QVariant cachedValue = model()->headerData(logicalIndex, orientation(), 0366 KisTimeBasedItemModel::FrameCachedRole); 0367 0368 QVariant withinRangeValue = model()->headerData(logicalIndex, orientation(), 0369 KisTimeBasedItemModel::WithinClipRange); 0370 0371 const bool isActive = activeValue.isValid() && activeValue.toBool(); 0372 const bool isCached = cachedValue.isValid() && cachedValue.toBool(); 0373 const bool isWithinRange = withinRangeValue.isValid() && withinRangeValue.toBool(); 0374 0375 if (isActive) { 0376 fillColor = KisAnimTimelineColors::instance()->headerActive(); 0377 } else if (isCached && isWithinRange) { 0378 fillColor = KisAnimTimelineColors::instance()->headerCachedFrame(); 0379 } 0380 0381 painter->fillRect(frameRect, fillColor); 0382 0383 QVector<QLine> lines; 0384 lines << QLine(p1, p2); 0385 lines << QLine(frameRect.topRight(), frameRect.bottomRight()); 0386 lines << QLine(frameRect.bottomLeft(), frameRect.bottomRight()); 0387 0388 QPen oldPen = painter->pen(); 0389 painter->setPen(gridPen); 0390 painter->drawLines(lines); 0391 painter->setPen(oldPen); 0392 } 0393 } 0394 0395 void KisAnimTimelineTimeHeader::changeEvent(QEvent *event) 0396 { 0397 Q_UNUSED(event); 0398 0399 updateMinimumSize(); 0400 } 0401 0402 void KisAnimTimelineTimeHeader::setFramePerSecond(int fps) 0403 { 0404 m_d->fps = fps; 0405 update(); 0406 } 0407 0408 bool KisAnimTimelineTimeHeader::setZoom(qreal zoom) 0409 { 0410 qreal newSectionSize = zoom * m_d->unitSectionSize; 0411 0412 if (newSectionSize < m_d->minSectionSize) { 0413 newSectionSize = m_d->minSectionSize; 0414 zoom = qreal(newSectionSize) / m_d->unitSectionSize; 0415 } else if (newSectionSize > m_d->maxSectionSize) { 0416 newSectionSize = m_d->maxSectionSize; 0417 zoom = qreal(newSectionSize) / m_d->unitSectionSize; 0418 } 0419 0420 m_d->remainder = newSectionSize - floor(newSectionSize); 0421 0422 if (newSectionSize != defaultSectionSize()) { 0423 setDefaultSectionSize(newSectionSize); 0424 emit sigZoomChanged(zoom); 0425 return true; 0426 } 0427 0428 return false; 0429 } 0430 0431 qreal KisAnimTimelineTimeHeader::zoom() { 0432 return (qreal(defaultSectionSize() + m_d->remainder) / m_d->unitSectionSize); 0433 } 0434 0435 void KisAnimTimelineTimeHeader::updateMinimumSize() 0436 { 0437 QFontMetrics metrics(this->font()); 0438 const int textHeight = metrics.height(); 0439 0440 setMinimumSize(0, 1.5 * textHeight); 0441 } 0442 0443 void KisAnimTimelineTimeHeader::setModel(QAbstractItemModel *model) 0444 { 0445 KisTimeBasedItemModel *framesModel = qobject_cast<KisTimeBasedItemModel*>(model); 0446 m_d->model = framesModel; 0447 0448 QHeaderView::setModel(model); 0449 } 0450 0451 int getColumnCount(const QModelIndexList &indexes, int *leftmostCol, int *rightmostCol) 0452 { 0453 QVector<int> columns; 0454 int leftmost = std::numeric_limits<int>::max(); 0455 int rightmost = std::numeric_limits<int>::min(); 0456 0457 Q_FOREACH (const QModelIndex &index, indexes) { 0458 leftmost = qMin(leftmost, index.column()); 0459 rightmost = qMax(rightmost, index.column()); 0460 if (!columns.contains(index.column())) { 0461 columns.append(index.column()); 0462 } 0463 } 0464 0465 if (leftmostCol) *leftmostCol = leftmost; 0466 if (rightmostCol) *rightmostCol = rightmost; 0467 0468 return columns.size(); 0469 } 0470 0471 void KisAnimTimelineTimeHeader::mousePressEvent(QMouseEvent *e) 0472 { 0473 int logical = logicalIndexAt(e->pos()); 0474 if (logical != -1) { 0475 QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); 0476 int numSelectedColumns = getColumnCount(selectedIndexes, 0, 0); 0477 0478 if (e->button() == Qt::RightButton) { 0479 if (numSelectedColumns <= 1) { 0480 model()->setHeaderData(logical, orientation(), true, KisTimeBasedItemModel::ActiveFrameRole); 0481 model()->setHeaderData(logical, orientation(), QVariant(int(SEEK_FINALIZE | SEEK_PUSH_AUDIO)), KisTimeBasedItemModel::ScrubToRole); 0482 } 0483 0484 /* Fix for safe-assert involving kis_animation_curve_docker. 0485 * There should probably be a more elegant way for dealing 0486 * with reused timeline_ruler_header instances in other 0487 * timeline views instead of simply animation_frame_view. 0488 * 0489 * This works for now though... */ 0490 if(!m_d->actionMan){ 0491 return; 0492 } 0493 0494 QMenu menu; 0495 0496 menu.addSection(i18n("Edit Columns:")); 0497 menu.addSeparator(); 0498 0499 KisActionManager::safePopulateMenu(&menu, "cut_columns_to_clipboard", m_d->actionMan); 0500 KisActionManager::safePopulateMenu(&menu, "copy_columns_to_clipboard", m_d->actionMan); 0501 KisActionManager::safePopulateMenu(&menu, "paste_columns_from_clipboard", m_d->actionMan); 0502 0503 menu.addSeparator(); 0504 0505 { //Frame Columns Submenu 0506 QMenu *frames = menu.addMenu(i18nc("@item:inmenu", "Keyframe Columns")); 0507 KisActionManager::safePopulateMenu(frames, "insert_column_left", m_d->actionMan); 0508 KisActionManager::safePopulateMenu(frames, "insert_column_right", m_d->actionMan); 0509 frames->addSeparator(); 0510 KisActionManager::safePopulateMenu(frames, "insert_multiple_columns", m_d->actionMan); 0511 } 0512 0513 { //Hold Columns Submenu 0514 QMenu *hold = menu.addMenu(i18nc("@item:inmenu", "Hold Frame Columns")); 0515 KisActionManager::safePopulateMenu(hold, "insert_hold_column", m_d->actionMan); 0516 KisActionManager::safePopulateMenu(hold, "remove_hold_column", m_d->actionMan); 0517 hold->addSeparator(); 0518 KisActionManager::safePopulateMenu(hold, "insert_multiple_hold_columns", m_d->actionMan); 0519 KisActionManager::safePopulateMenu(hold, "remove_multiple_hold_columns", m_d->actionMan); 0520 } 0521 0522 menu.addSeparator(); 0523 0524 KisActionManager::safePopulateMenu(&menu, "remove_columns", m_d->actionMan); 0525 KisActionManager::safePopulateMenu(&menu, "remove_columns_and_pull", m_d->actionMan); 0526 0527 if (numSelectedColumns > 1) { 0528 menu.addSeparator(); 0529 KisActionManager::safePopulateMenu(&menu, "mirror_columns", m_d->actionMan); 0530 } 0531 0532 menu.addSeparator(); 0533 0534 KisActionManager::safePopulateMenu(&menu, "clear_animation_cache", m_d->actionMan); 0535 0536 menu.exec(e->globalPos()); 0537 0538 return; 0539 0540 } else if (e->button() == Qt::LeftButton) { 0541 m_d->lastPressSectionIndex = logical; 0542 model()->setHeaderData(logical, orientation(), true, KisTimeBasedItemModel::ActiveFrameRole); 0543 } 0544 } 0545 0546 QHeaderView::mousePressEvent(e); 0547 } 0548 0549 void KisAnimTimelineTimeHeader::mouseMoveEvent(QMouseEvent *e) 0550 { 0551 int logical = logicalIndexAt(e->pos()); 0552 if (logical != -1) { 0553 0554 if (e->buttons() & Qt::LeftButton) { 0555 0556 m_d->model->setScrubState(true); 0557 QVariant activeValue = model()->headerData(logical, orientation(), KisTimeBasedItemModel::ActiveFrameRole); 0558 KIS_ASSERT(activeValue.type() == QVariant::Bool); 0559 if (activeValue.toBool() != true) { 0560 model()->setHeaderData(logical, orientation(), true, KisTimeBasedItemModel::ActiveFrameRole); 0561 model()->setHeaderData(logical, orientation(), QVariant(int(SEEK_PUSH_AUDIO)), KisTimeBasedItemModel::ScrubToRole); 0562 } 0563 0564 if (m_d->lastPressSectionIndex >= 0 && 0565 logical != m_d->lastPressSectionIndex && 0566 e->modifiers() & Qt::ShiftModifier) { 0567 0568 const int minCol = qMin(m_d->lastPressSectionIndex, logical); 0569 const int maxCol = qMax(m_d->lastPressSectionIndex, logical); 0570 0571 QItemSelection sel(m_d->model->index(0, minCol), m_d->model->index(0, maxCol)); 0572 selectionModel()->select(sel, 0573 QItemSelectionModel::Columns | 0574 QItemSelectionModel::SelectCurrent); 0575 } 0576 0577 } 0578 0579 } 0580 0581 QHeaderView::mouseMoveEvent(e); 0582 } 0583 0584 int KisAnimTimelineTimeHeader::estimateLastVisibleColumn() 0585 { 0586 const int sectionWidth = defaultSectionSize(); 0587 return (m_d->offset + width() - 1) / sectionWidth; 0588 } 0589 0590 int KisAnimTimelineTimeHeader::estimateFirstVisibleColumn() 0591 { 0592 const int sectionWidth = defaultSectionSize(); 0593 return ceil(qreal(m_d->offset) / sectionWidth); 0594 } 0595 0596 void KisAnimTimelineTimeHeader::mouseReleaseEvent(QMouseEvent *e) 0597 { 0598 if (!m_d->model) 0599 return; 0600 0601 if (e->button() == Qt::LeftButton) { 0602 int timeUnderMouse = qMax(logicalIndexAt(e->pos()), 0); 0603 model()->setHeaderData(timeUnderMouse, orientation(), true, KisTimeBasedItemModel::ActiveFrameRole); 0604 if (timeUnderMouse != m_d->model->currentTime()) { 0605 model()->setHeaderData(timeUnderMouse, orientation(), QVariant(int(SEEK_PUSH_AUDIO | SEEK_FINALIZE)), KisTimeBasedItemModel::ScrubToRole); 0606 } 0607 m_d->model->setScrubState(false); 0608 } 0609 0610 QHeaderView::mouseReleaseEvent(e); 0611 } 0612 0613 QModelIndexList KisAnimTimelineTimeHeader::Private::prepareFramesSlab(int startCol, int endCol) 0614 { 0615 QModelIndexList frames; 0616 0617 const int numRows = model->rowCount(); 0618 0619 for (int i = 0; i < numRows; i++) { 0620 for (int j = startCol; j <= endCol; j++) { 0621 QModelIndex index = model->index(i, j); 0622 const bool exists = model->data(index, KisTimeBasedItemModel::FrameExistsRole).toBool(); 0623 if (exists) { 0624 frames << index; 0625 } 0626 } 0627 } 0628 0629 return frames; 0630 }