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 }