File indexing completed on 2024-05-12 04:52:48

0001 /*
0002     SPDX-FileCopyrightText: 2011 Till Theato <root@ttill.de>
0003     SPDX-FileCopyrightText: 2017 Nicolas Carion
0004     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "keyframeview.hpp"
0008 #include "assets/keyframes/model/keyframemodellist.hpp"
0009 #include "core.h"
0010 #include "kdenlivesettings.h"
0011 
0012 #include <QApplication>
0013 #include <QMouseEvent>
0014 #include <QStylePainter>
0015 #include <QtMath>
0016 
0017 #include <KColorScheme>
0018 #include <QFontDatabase>
0019 #include <utility>
0020 
0021 KeyframeView::KeyframeView(std::shared_ptr<KeyframeModelList> model, int duration, QWidget *parent)
0022     : QWidget(parent)
0023     , m_model(std::move(model))
0024     , m_duration(duration)
0025     , m_position(0)
0026     , m_currentKeyframeOriginal(-1)
0027     , m_hoverKeyframe(-1)
0028     , m_scale(1)
0029     , m_zoomFactor(1)
0030     , m_zoomStart(0)
0031     , m_moveKeyframeMode(false)
0032     , m_keyframeZonePress(false)
0033     , m_clickPoint(-1)
0034     , m_clickEnd(-1)
0035     , m_zoomHandle(0, 1)
0036     , m_hoverZoomIn(false)
0037     , m_hoverZoomOut(false)
0038     , m_hoverZoom(false)
0039     , m_clickOffset(0)
0040 {
0041     setMouseTracking(true);
0042     setMinimumSize(QSize(150, 20));
0043     setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0044     QPalette p = palette();
0045     KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window);
0046     m_colSelected = palette().highlight().color();
0047     m_colKeyframe = scheme.foreground(KColorScheme::NormalText).color();
0048     m_size = QFontInfo(font()).pixelSize() * 3;
0049     m_lineHeight = int(m_size / 2.1);
0050     m_zoomHeight = m_size * 3 / 4;
0051     m_offset = m_size / 4;
0052     setFixedHeight(m_size);
0053     setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
0054     connect(m_model.get(), &KeyframeModelList::modelChanged, this, &KeyframeView::slotModelChanged);
0055     connect(m_model.get(), &KeyframeModelList::modelDisplayChanged, this, &KeyframeView::slotModelDisplayChanged);
0056     m_centerConnection = connect(this, &KeyframeView::updateKeyframeOriginal, this, [&](int pos) {
0057         m_currentKeyframeOriginal = pos;
0058         update();
0059     });
0060 }
0061 
0062 KeyframeView::~KeyframeView()
0063 {
0064     QObject::disconnect(m_centerConnection);
0065 }
0066 
0067 void KeyframeView::slotModelChanged()
0068 {
0069     int offset = pCore->getItemIn(m_model->getOwnerId());
0070     Q_EMIT atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
0071     Q_EMIT modified();
0072     update();
0073 }
0074 
0075 void KeyframeView::slotModelDisplayChanged()
0076 {
0077     int offset = pCore->getItemIn(m_model->getOwnerId());
0078     Q_EMIT atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
0079     update();
0080 }
0081 
0082 void KeyframeView::slotSetPosition(int pos, bool isInRange)
0083 {
0084     if (!isInRange) {
0085         m_position = -1;
0086         update();
0087         return;
0088     }
0089     if (pos != m_position) {
0090         m_position = pos;
0091         int offset = pCore->getItemIn(m_model->getOwnerId());
0092         Q_EMIT atKeyframe(m_model->hasKeyframe(pos + offset), m_model->singleKeyframe());
0093         double zoomPos = double(m_position) / m_duration;
0094         if (zoomPos < m_zoomHandle.x()) {
0095             double interval = m_zoomHandle.y() - m_zoomHandle.x();
0096             zoomPos = qBound(0.0, zoomPos - interval / 5, 1.0);
0097             m_zoomHandle.setX(zoomPos);
0098             m_zoomHandle.setY(zoomPos + interval);
0099         } else if (zoomPos > m_zoomHandle.y()) {
0100             double interval = m_zoomHandle.y() - m_zoomHandle.x();
0101             zoomPos = qBound(0.0, zoomPos + interval / 5, 1.0);
0102             m_zoomHandle.setX(zoomPos - interval);
0103             m_zoomHandle.setY(zoomPos);
0104         }
0105         update();
0106     }
0107 }
0108 
0109 void KeyframeView::initKeyframePos()
0110 {
0111     int offset = pCore->getItemIn(m_model->getOwnerId());
0112     Q_EMIT atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
0113 }
0114 
0115 const QVector<int> KeyframeView::selectedKeyframesIndexes()
0116 {
0117     return m_model->selectedKeyframes();
0118 }
0119 
0120 void KeyframeView::slotDuplicateKeyframe()
0121 {
0122     int offset = pCore->getItemIn(m_model->getOwnerId());
0123     if (m_model->activeKeyframe() > -1 && !m_model->hasKeyframe(m_position + offset)) {
0124         Fun undo = []() { return true; };
0125         Fun redo = []() { return true; };
0126         int delta = offset + m_position - m_model->getPosAtIndex(m_model->activeKeyframe()).frames(pCore->getCurrentFps());
0127         for (int &kf : m_model->selectedKeyframes()) {
0128             int kfrPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps());
0129             m_model->duplicateKeyframeWithUndo(GenTime(kfrPos, pCore->getCurrentFps()), GenTime(kfrPos + delta, pCore->getCurrentFps()), undo, redo);
0130         }
0131         pCore->pushUndo(undo, redo, i18n("Duplicate keyframe"));
0132     }
0133 }
0134 
0135 bool KeyframeView::slotAddKeyframe(int pos)
0136 {
0137     if (pos < 0) {
0138         pos = m_position;
0139     }
0140     int offset = pCore->getItemIn(m_model->getOwnerId());
0141     return m_model->addKeyframe(GenTime(pos + offset, pCore->getCurrentFps()), KeyframeType(KdenliveSettings::defaultkeyframeinterp()));
0142 }
0143 
0144 const QString KeyframeView::getAssetId()
0145 {
0146     return m_model->getAssetId();
0147 }
0148 
0149 void KeyframeView::slotAddRemove()
0150 {
0151     Q_EMIT activateEffect();
0152     int offset = pCore->getItemIn(m_model->getOwnerId());
0153     if (m_model->hasKeyframe(m_position + offset)) {
0154         if (m_model->selectedKeyframes().contains(m_position)) {
0155             // Delete all selected keyframes
0156             slotRemoveKeyframe(m_model->selectedKeyframes());
0157         } else {
0158             slotRemoveKeyframe({m_position});
0159         }
0160     } else {
0161         if (slotAddKeyframe(m_position)) {
0162             GenTime position(m_position + offset, pCore->getCurrentFps());
0163             int currentIx = m_model->getIndexForPos(position);
0164             if (currentIx > -1) {
0165                 m_model->setSelectedKeyframes({currentIx});
0166                 m_model->setActiveKeyframe(currentIx);
0167             }
0168         }
0169     }
0170 }
0171 
0172 void KeyframeView::slotEditType(int type, const QPersistentModelIndex &index)
0173 {
0174     int offset = pCore->getItemIn(m_model->getOwnerId());
0175     if (m_model->hasKeyframe(m_position + offset)) {
0176         m_model->updateKeyframeType(GenTime(m_position + offset, pCore->getCurrentFps()), type, index);
0177     }
0178 }
0179 
0180 void KeyframeView::slotRemoveKeyframe(const QVector<int> &positions)
0181 {
0182     if (m_model->singleKeyframe()) {
0183         // Don't allow zero keyframe
0184         pCore->displayMessage(i18n("Cannot remove the last keyframe"), MessageType::ErrorMessage, 500);
0185         return;
0186     }
0187     int offset = pCore->getItemIn(m_model->getOwnerId());
0188     Fun undo = []() { return true; };
0189     Fun redo = []() { return true; };
0190     for (int pos : positions) {
0191         if (pos == 0) {
0192             // Don't allow moving first keyframe
0193             continue;
0194         }
0195         m_model->removeKeyframeWithUndo(GenTime(pos + offset, pCore->getCurrentFps()), undo, redo);
0196     }
0197     pCore->pushUndo(undo, redo, i18np("Remove keyframe", "Remove keyframes", positions.size()));
0198 }
0199 
0200 void KeyframeView::setDuration(int duration)
0201 {
0202     m_duration = duration;
0203     int offset = pCore->getItemIn(m_model->getOwnerId());
0204     Q_EMIT atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
0205     // Unselect keyframes that are outside range if any
0206     QVector<int> toDelete;
0207     int kfrIx = 0;
0208     for (auto &p : m_model->selectedKeyframes()) {
0209         int kfPos = m_model->getPosAtIndex(p).frames(pCore->getCurrentFps());
0210         if (kfPos < offset || kfPos >= offset + m_duration) {
0211             toDelete << kfrIx;
0212         }
0213         kfrIx++;
0214     }
0215     for (auto &p : toDelete) {
0216         m_model->removeFromSelected(p);
0217     }
0218     update();
0219 }
0220 
0221 void KeyframeView::slotGoToNext()
0222 {
0223     Q_EMIT activateEffect();
0224     if (m_position == m_duration - 1) {
0225         return;
0226     }
0227 
0228     bool ok;
0229     int offset = pCore->getItemIn(m_model->getOwnerId());
0230     auto next = m_model->getNextKeyframe(GenTime(m_position + offset, pCore->getCurrentFps()), &ok);
0231 
0232     if (ok) {
0233         Q_EMIT seekToPos(qMin(int(next.first.frames(pCore->getCurrentFps())) - offset, m_duration - 1));
0234     } else {
0235         // no keyframe after current position
0236         Q_EMIT seekToPos(m_duration - 1);
0237     }
0238 }
0239 
0240 void KeyframeView::slotGoToPrev()
0241 {
0242     Q_EMIT activateEffect();
0243     if (m_position == 0) {
0244         return;
0245     }
0246 
0247     bool ok;
0248     int offset = pCore->getItemIn(m_model->getOwnerId());
0249     auto prev = m_model->getPrevKeyframe(GenTime(m_position + offset, pCore->getCurrentFps()), &ok);
0250 
0251     if (ok) {
0252         Q_EMIT seekToPos(qMax(0, int(prev.first.frames(pCore->getCurrentFps())) - offset));
0253     } else {
0254         // no keyframe after current position
0255         Q_EMIT seekToPos(m_duration - 1);
0256     }
0257 }
0258 
0259 void KeyframeView::slotCenterKeyframe()
0260 {
0261     if (m_currentKeyframeOriginal == -1 || m_currentKeyframeOriginal == m_position || m_currentKeyframeOriginal == 0) {
0262         return;
0263     }
0264     int offset = pCore->getItemIn(m_model->getOwnerId());
0265     if (!m_model->hasKeyframe(m_currentKeyframeOriginal)) {
0266         return;
0267     }
0268     Fun undo = []() { return true; };
0269     Fun redo = []() { return true; };
0270     int delta = m_position - (m_currentKeyframeOriginal - offset);
0271     int sourcePosition = m_currentKeyframeOriginal;
0272     QVector<int> updatedSelection;
0273     for (int &kf : m_model->selectedKeyframes()) {
0274         if (kf == 0) {
0275             // Don't allow moving first keyframe
0276             updatedSelection << 0;
0277             continue;
0278         }
0279         int kfPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps());
0280         GenTime initPos(kfPos, pCore->getCurrentFps());
0281         GenTime targetPos(kfPos + delta, pCore->getCurrentFps());
0282         m_model->moveKeyframeWithUndo(initPos, targetPos, undo, redo);
0283         break;
0284     }
0285     Fun local_redo = [this, position = m_position]() {
0286         Q_EMIT updateKeyframeOriginal(position);
0287         return true;
0288     };
0289     Fun local_undo = [this, sourcePosition]() {
0290         Q_EMIT updateKeyframeOriginal(sourcePosition);
0291         return true;
0292     };
0293     local_redo();
0294     PUSH_LAMBDA(local_redo, redo);
0295     PUSH_FRONT_LAMBDA(local_undo, undo);
0296     pCore->pushUndo(undo, redo, i18nc("@action", "Move keyframe"));
0297 }
0298 
0299 void KeyframeView::mousePressEvent(QMouseEvent *event)
0300 {
0301     Q_EMIT activateEffect();
0302     int offset = pCore->getItemIn(m_model->getOwnerId());
0303     double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
0304     double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
0305     double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
0306 
0307 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0308     int pos = int(((event->x() - m_offset) / zoomFactor + zoomStart) / m_scale);
0309 #else
0310     int pos = int(((event->position().x() - m_offset) / zoomFactor + zoomStart) / m_scale);
0311 #endif
0312     pos = qBound(0, pos, m_duration - 1);
0313     m_moveKeyframeMode = false;
0314     m_keyframeZonePress = false;
0315     if (event->button() == Qt::LeftButton) {
0316 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0317         if (event->y() < m_lineHeight) {
0318 #else
0319         if (event->position().y() < m_lineHeight) {
0320 #endif
0321             // mouse click in keyframes area
0322             bool ok;
0323             GenTime position(pos + offset, pCore->getCurrentFps());
0324             if (event->modifiers() & Qt::ShiftModifier) {
0325                 m_clickPoint = pos;
0326                 return;
0327             }
0328             m_keyframeZonePress = true;
0329             auto keyframe = m_model->getClosestKeyframe(position, &ok);
0330             if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
0331                 int currentIx = m_model->getIndexForPos(keyframe.first);
0332                 m_currentKeyframeOriginal = keyframe.first.frames(pCore->getCurrentFps());
0333                 if (event->modifiers() & Qt::ControlModifier) {
0334                     if (m_model->selectedKeyframes().contains(currentIx)) {
0335                         m_model->removeFromSelected(currentIx);
0336                         if (m_model->activeKeyframe() == currentIx) {
0337                             m_model->setActiveKeyframe(-1);
0338                         }
0339                         m_currentKeyframeOriginal = -1;
0340                     } else {
0341                         m_model->appendSelectedKeyframe(currentIx);
0342                         m_model->setActiveKeyframe(currentIx);
0343                     }
0344                 } else if (!m_model->selectedKeyframes().contains(currentIx)) {
0345                     m_model->setSelectedKeyframes({currentIx});
0346                     m_model->setActiveKeyframe(currentIx);
0347                 } else {
0348                     m_model->setActiveKeyframe(currentIx);
0349                 }
0350                 // Select and seek to keyframe
0351                 if (m_currentKeyframeOriginal > -1) {
0352                     if (KdenliveSettings::keyframeseek()) {
0353                         Q_EMIT seekToPos(m_currentKeyframeOriginal - offset);
0354                     }
0355                 }
0356                 return;
0357             }
0358             // no keyframe next to mouse
0359             m_model->setSelectedKeyframes({});
0360             m_model->setActiveKeyframe(-1);
0361             m_currentKeyframeOriginal = -1;
0362 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0363         } else if (event->y() > m_zoomHeight + 2) {
0364 #else
0365         } else if (event->position().y() > m_zoomHeight + 2) {
0366 #endif
0367             // click on zoom area
0368             if (m_hoverZoom) {
0369 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0370                 m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
0371 #else
0372                 m_clickOffset = (double(event->position().x()) - m_offset) / (width() - 2 * m_offset);
0373 #endif
0374             }
0375             // When not zoomed, allow seek by clicking on zoombar
0376             if (qFuzzyCompare(m_zoomFactor, 1.) && pos != m_position && !m_hoverZoomIn && !m_hoverZoomOut) {
0377                 Q_EMIT seekToPos(pos);
0378             }
0379             return;
0380         }
0381 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0382     } else if (event->button() == Qt::RightButton && event->y() > m_zoomHeight + 2) {
0383 #else
0384     } else if (event->button() == Qt::RightButton && event->position().y() > m_zoomHeight + 2) {
0385 #endif
0386         // Right click on zoom, switch between no zoom and last zoom status
0387         if (m_zoomHandle == QPointF(0, 1)) {
0388             if (!m_lastZoomHandle.isNull()) {
0389                 m_zoomHandle = m_lastZoomHandle;
0390                 update();
0391                 return;
0392             }
0393         } else {
0394             m_lastZoomHandle = m_zoomHandle;
0395             m_zoomHandle = QPointF(0, 1);
0396             update();
0397             return;
0398         }
0399     }
0400     if (pos != m_position) {
0401         Q_EMIT seekToPos(pos);
0402         update();
0403     }
0404 }
0405 
0406 void KeyframeView::mouseMoveEvent(QMouseEvent *event)
0407 {
0408     int offset = pCore->getItemIn(m_model->getOwnerId());
0409     double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
0410     double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
0411     double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
0412 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0413     int pos = int(((double(event->x()) - m_offset) / zoomFactor + zoomStart) / m_scale);
0414 #else
0415     int pos = int(((double(event->position().x()) - m_offset) / zoomFactor + zoomStart) / m_scale);
0416 #endif
0417     pos = qBound(0, pos, m_duration - 1);
0418     GenTime position(pos + offset, pCore->getCurrentFps());
0419     if ((event->buttons() & Qt::LeftButton) != 0u) {
0420         if (m_hoverZoomIn || m_hoverZoomOut || m_hoverZoom) {
0421             // Moving zoom handles
0422             if (m_hoverZoomIn) {
0423 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0424                 m_zoomHandle.setX(qMin(qMax(0., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.y() - 0.015));
0425 #else
0426                 m_zoomHandle.setX(qMin(qMax(0., double(event->position().x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.y() - 0.015));
0427 #endif
0428                 update();
0429                 return;
0430             }
0431             if (m_hoverZoomOut) {
0432 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0433                 m_zoomHandle.setY(qMax(qMin(1., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.x() + 0.015));
0434 #else
0435                 m_zoomHandle.setY(qMax(qMin(1., double(event->position().x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.x() + 0.015));
0436 #endif
0437                 update();
0438                 return;
0439             }
0440             // moving zoom zone
0441             if (m_hoverZoom) {
0442 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0443                 double clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset) - m_clickOffset;
0444 #else
0445                 double clickOffset = (double(event->position().x()) - m_offset) / (width() - 2 * m_offset) - m_clickOffset;
0446 #endif
0447                 double newX = m_zoomHandle.x() + clickOffset;
0448                 if (newX < 0) {
0449                     clickOffset = -m_zoomHandle.x();
0450                     newX = 0;
0451                 }
0452                 double newY = m_zoomHandle.y() + clickOffset;
0453                 if (newY > 1) {
0454                     clickOffset = 1 - m_zoomHandle.y();
0455                     newY = 1;
0456                     newX = m_zoomHandle.x() + clickOffset;
0457                 }
0458 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0459                 m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
0460 #else
0461                 m_clickOffset = (double(event->position().x()) - m_offset) / (width() - 2 * m_offset);
0462 #endif
0463                 m_zoomHandle = QPointF(newX, newY);
0464                 update();
0465             }
0466             return;
0467         }
0468         if (m_model->activeKeyframe() == pos) {
0469             return;
0470         }
0471         if (m_model->activeKeyframe() > 0 && m_currentKeyframeOriginal > -1 && m_clickPoint == -1 &&
0472             (m_moveKeyframeMode ||
0473              (qAbs(pos - (m_currentKeyframeOriginal - offset)) * m_scale * m_zoomFactor < QApplication::startDragDistance() && m_keyframeZonePress))) {
0474             m_moveKeyframeMode = true;
0475             if (!m_model->hasKeyframe(pos + offset)) {
0476                 int delta = pos - (m_model->getPosAtIndex(m_model->activeKeyframe()).frames(pCore->getCurrentFps()) - offset);
0477                 // Check that the move is possible
0478                 for (int &kf : m_model->selectedKeyframes()) {
0479                     int updatedPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps()) + delta;
0480                     if (m_model->hasKeyframe(updatedPos)) {
0481                         // Don't allow moving over another keyframe
0482                         return;
0483                     }
0484                 }
0485                 for (int &kf : m_model->selectedKeyframes()) {
0486                     if (kf == 0) {
0487                         // Don't allow moving first keyframe
0488                         continue;
0489                     }
0490                     int kfPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps());
0491                     GenTime currentPos(kfPos, pCore->getCurrentFps());
0492                     GenTime updatedPos(kfPos + delta, pCore->getCurrentFps());
0493                     if (!m_model->moveKeyframe(currentPos, updatedPos, false)) {
0494                         qDebug() << "=== FAILED KF MOVE!!!";
0495                         Q_ASSERT(false);
0496                     }
0497                     // We only move first keyframe, the other are moved in the model command
0498                     break;
0499                 }
0500             }
0501         }
0502         // Rubberband selection
0503         if (m_clickPoint >= 0) {
0504             m_clickEnd = pos;
0505             int min = qMin(m_clickPoint, m_clickEnd);
0506             int max = qMax(m_clickPoint, m_clickEnd);
0507             min = qMax(1, min);
0508             m_model->setSelectedKeyframes({});
0509             m_model->setActiveKeyframe(-1);
0510             m_currentKeyframeOriginal = -1;
0511             double fps = pCore->getCurrentFps();
0512             int kfrIx = 0;
0513             for (const auto &keyframe : *m_model.get()) {
0514                 int kfPos = keyframe.first.frames(fps) - offset;
0515                 if (kfPos > min && kfPos <= max) {
0516                     m_model->appendSelectedKeyframe(kfrIx);
0517                 }
0518                 kfrIx++;
0519             }
0520             if (!m_model->selectedKeyframes().isEmpty()) {
0521                 m_model->setActiveKeyframe(m_model->selectedKeyframes().first());
0522                 m_currentKeyframeOriginal = m_model->getPosAtIndex(m_model->selectedKeyframes().first()).frames(pCore->getCurrentFps());
0523             }
0524             update();
0525             return;
0526         }
0527 
0528         if (!m_moveKeyframeMode || KdenliveSettings::keyframeseek()) {
0529             if (pos != m_position) {
0530                 Q_EMIT seekToPos(pos);
0531             }
0532         }
0533         return;
0534     }
0535 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0536     if (event->y() < m_lineHeight) {
0537 #else
0538     if (event->position().y() < m_lineHeight) {
0539 #endif
0540         bool ok;
0541         auto keyframe = m_model->getClosestKeyframe(position, &ok);
0542         if (ok && qAbs(((position.frames(pCore->getCurrentFps()) - keyframe.first.frames(pCore->getCurrentFps())) * m_scale) * m_zoomFactor) <
0543                       QApplication::startDragDistance()) {
0544             m_hoverKeyframe = keyframe.first.frames(pCore->getCurrentFps()) - offset;
0545             setCursor(Qt::PointingHandCursor);
0546             setToolTip(KeyframeTypeName.value(keyframe.second));
0547             m_hoverZoomIn = false;
0548             m_hoverZoomOut = false;
0549             m_hoverZoom = false;
0550             update();
0551             return;
0552         }
0553     }
0554 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0555     if (event->y() > m_zoomHeight + 2) {
0556         // Moving in zoom area
0557         if (qAbs(event->x() - m_offset - (m_zoomHandle.x() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
0558 #else
0559     if (event->position().y() > m_zoomHeight + 2) {
0560         // Moving in zoom area
0561         if (qAbs(event->position().x() - m_offset - (m_zoomHandle.x() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
0562 #endif
0563             setCursor(Qt::SizeHorCursor);
0564             m_hoverZoomIn = true;
0565             m_hoverZoomOut = false;
0566             m_hoverZoom = false;
0567             update();
0568             return;
0569         }
0570 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0571         if (qAbs(event->x() - m_offset - (m_zoomHandle.y() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
0572 #else
0573         if (qAbs(event->position().x() - m_offset - (m_zoomHandle.y() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
0574 #endif
0575             setCursor(Qt::SizeHorCursor);
0576             m_hoverZoomOut = true;
0577             m_hoverZoomIn = false;
0578             m_hoverZoom = false;
0579             update();
0580             return;
0581         }
0582 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0583         if (m_zoomHandle != QPointF(0, 1) && event->x() > m_offset + (m_zoomHandle.x() * (width() - 2 * m_offset)) &&
0584             event->x() < m_offset + (m_zoomHandle.y() * (width() - 2 * m_offset))) {
0585 #else
0586         if (m_zoomHandle != QPointF(0, 1) && event->position().x() > m_offset + (m_zoomHandle.x() * (width() - 2 * m_offset)) &&
0587             event->position().x() < m_offset + (m_zoomHandle.y() * (width() - 2 * m_offset))) {
0588 #endif
0589             setCursor(Qt::PointingHandCursor);
0590             m_hoverZoom = true;
0591             m_hoverZoomIn = false;
0592             m_hoverZoomOut = false;
0593             update();
0594             return;
0595         }
0596     }
0597 
0598     if (m_hoverKeyframe != -1 || m_hoverZoomOut || m_hoverZoomIn || m_hoverZoom) {
0599         m_hoverKeyframe = -1;
0600         m_hoverZoomOut = false;
0601         m_hoverZoomIn = false;
0602         m_hoverZoom = false;
0603         setCursor(Qt::ArrowCursor);
0604         update();
0605     }
0606 }
0607 
0608 void KeyframeView::mouseReleaseEvent(QMouseEvent *event)
0609 {
0610     Q_UNUSED(event)
0611     m_clickPoint = -1;
0612     if (m_clickEnd >= 0) {
0613         m_clickEnd = -1;
0614         update();
0615     }
0616     if (m_moveKeyframeMode && m_model->activeKeyframe() >= 0 &&
0617         m_currentKeyframeOriginal != m_model->getPosAtIndex(m_model->activeKeyframe()).frames(pCore->getCurrentFps())) {
0618         int delta = m_model->getPosAtIndex(m_model->activeKeyframe()).frames(pCore->getCurrentFps()) - m_currentKeyframeOriginal;
0619         // Move back all keyframes to their initial positions
0620         for (int &kf : m_model->selectedKeyframes()) {
0621             if (kf == 0) {
0622                 // Don't allow moving first keyframe
0623                 continue;
0624             }
0625             int kfPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps());
0626             GenTime initPos(kfPos - delta, pCore->getCurrentFps());
0627             GenTime targetPos(kfPos, pCore->getCurrentFps());
0628             m_model->moveKeyframe(targetPos, initPos, false, true);
0629             break;
0630         }
0631         // Move all keyframes to their new positions
0632         Fun undo = []() { return true; };
0633         Fun redo = []() { return true; };
0634         for (int &kf : m_model->selectedKeyframes()) {
0635             if (kf == 0) {
0636                 // Don't allow moving first keyframe
0637                 continue;
0638             }
0639             int kfPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps());
0640             GenTime initPos(kfPos, pCore->getCurrentFps());
0641             GenTime targetPos(kfPos + delta, pCore->getCurrentFps());
0642             m_model->moveKeyframeWithUndo(initPos, targetPos, undo, redo);
0643             break;
0644         }
0645         m_currentKeyframeOriginal = m_model->getPosAtIndex(m_model->activeKeyframe()).frames(pCore->getCurrentFps());
0646         pCore->pushUndo(undo, redo, i18np("Move keyframe", "Move keyframes", m_model->selectedKeyframes().size()));
0647         qDebug() << "RELEASING keyframe move" << delta;
0648     }
0649     m_moveKeyframeMode = false;
0650     m_keyframeZonePress = false;
0651 }
0652 
0653 void KeyframeView::mouseDoubleClickEvent(QMouseEvent *event)
0654 {
0655 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0656     if (event->button() == Qt::LeftButton && event->y() < m_lineHeight) {
0657 #else
0658     if (event->button() == Qt::LeftButton && event->position().y() < m_lineHeight) {
0659 #endif
0660         int offset = pCore->getItemIn(m_model->getOwnerId());
0661         double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
0662         double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
0663         double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
0664 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0665         int pos = int(((event->x() - m_offset) / zoomFactor + zoomStart) / m_scale);
0666 #else
0667         int pos = int(((event->position().x() - m_offset) / zoomFactor + zoomStart) / m_scale);
0668 #endif
0669         pos = qBound(0, pos, m_duration - 1);
0670         GenTime position(pos + offset, pCore->getCurrentFps());
0671         bool ok;
0672         auto keyframe = m_model->getClosestKeyframe(position, &ok);
0673         if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
0674             if (keyframe.first.frames(pCore->getCurrentFps()) != offset) {
0675                 m_model->removeKeyframe(keyframe.first);
0676                 m_currentKeyframeOriginal = -1;
0677                 if (keyframe.first.frames(pCore->getCurrentFps()) == m_position + offset) {
0678                     Q_EMIT atKeyframe(false, m_model->singleKeyframe());
0679                 }
0680             }
0681             return;
0682         }
0683 
0684         // add new keyframe
0685         m_model->addKeyframe(position, KeyframeType(KdenliveSettings::defaultkeyframeinterp()));
0686     } else {
0687         QWidget::mouseDoubleClickEvent(event);
0688     }
0689 }
0690 
0691 void KeyframeView::wheelEvent(QWheelEvent *event)
0692 {
0693     if (event->modifiers() & Qt::AltModifier) {
0694         // Alt modifier seems to invert x/y axis
0695         if (event->angleDelta().x() > 0) {
0696             slotGoToPrev();
0697         } else {
0698             slotGoToNext();
0699         }
0700         event->setAccepted(true);
0701         return;
0702     }
0703     if (event->modifiers() & Qt::ControlModifier) {
0704         int maxWidth = width() - 2 * m_offset;
0705         m_zoomStart = m_zoomHandle.x() * maxWidth;
0706         m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
0707         double scaledPos = m_position * m_scale;
0708         double zoomRange = (m_zoomHandle.y() - m_zoomHandle.x()) * maxWidth;
0709         if (event->angleDelta().y() > 0) {
0710             zoomRange /= 1.5;
0711         } else {
0712             zoomRange *= 1.5;
0713         }
0714         if (zoomRange < 5) {
0715             // Don't allow too small zoombar
0716             return;
0717         }
0718         double length = (scaledPos - zoomRange / 2) / maxWidth;
0719         m_zoomHandle.setX(qMax(0., length));
0720         if (length < 0) {
0721             m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth - length));
0722         } else {
0723             m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth));
0724         }
0725         update();
0726         event->setAccepted(true);
0727         return;
0728     }
0729     int change = event->angleDelta().y() > 0 ? -1 : 1;
0730     int pos = qBound(0, m_position + change, m_duration - 1);
0731     Q_EMIT seekToPos(pos);
0732     event->setAccepted(true);
0733 }
0734 
0735 void KeyframeView::paintEvent(QPaintEvent *event)
0736 {
0737     Q_UNUSED(event)
0738 
0739     QStylePainter p(this);
0740     int maxWidth = width() - 2 * m_offset;
0741     if (m_duration > 1) {
0742         m_scale = maxWidth / double(m_duration - 1);
0743     } else {
0744         m_scale = maxWidth;
0745     }
0746     int headOffset = m_lineHeight / 2;
0747     int offset = pCore->getItemIn(m_model->getOwnerId());
0748     m_zoomStart = m_zoomHandle.x() * maxWidth;
0749     m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
0750     int zoomEnd = qCeil(m_zoomHandle.y() * maxWidth);
0751     /* ticks */
0752     double fps = pCore->getCurrentFps();
0753     int displayedLength = int(m_duration / m_zoomFactor / fps);
0754     double factor = 1;
0755     if (displayedLength < 2) {
0756         // 1 frame tick
0757     } else if (displayedLength < 30) {
0758         // 1 sec tick
0759         factor = fps;
0760     } else if (displayedLength < 150) {
0761         // 5 sec tick
0762         factor = 5 * fps;
0763     } else if (displayedLength < 300) {
0764         // 10 sec tick
0765         factor = 10 * fps;
0766     } else if (displayedLength < 900) {
0767         // 30 sec tick
0768         factor = 30 * fps;
0769     } else if (displayedLength < 1800) {
0770         // 1 min. tick
0771         factor = 60 * fps;
0772     } else if (displayedLength < 9000) {
0773         // 5 min tick
0774         factor = 300 * fps;
0775     } else if (displayedLength < 18000) {
0776         // 10 min tick
0777         factor = 600 * fps;
0778     } else {
0779         // 30 min tick
0780         factor = 1800 * fps;
0781     }
0782 
0783     // Position of left border in frames
0784     double tickOffset = m_zoomStart * m_zoomFactor;
0785     double frameSize = factor * m_scale * m_zoomFactor;
0786     int base = int(tickOffset / frameSize);
0787     tickOffset = frameSize - (tickOffset - (base * frameSize));
0788     // Draw frame ticks
0789     for (int i = 0; i < maxWidth / frameSize; i++) {
0790         int scaledTick = int(m_offset + (i * frameSize) + tickOffset);
0791         if (scaledTick >= maxWidth + m_offset) {
0792             break;
0793         }
0794         p.drawLine(QPointF(scaledTick, m_lineHeight + 1), QPointF(scaledTick, m_lineHeight - 3));
0795     }
0796 
0797     /*
0798      * keyframes
0799      */
0800     int kfrIx = 0;
0801     QVector<int> selecteds = m_model->selectedKeyframes();
0802     for (const auto &keyframe : *m_model.get()) {
0803         int pos = keyframe.first.frames(fps) - offset;
0804         if (pos < 0) continue;
0805         double scaledPos = pos * m_scale;
0806         if (scaledPos < m_zoomStart || qFloor(scaledPos) > zoomEnd) {
0807             kfrIx++;
0808             continue;
0809         }
0810         if (kfrIx == m_model->activeKeyframe()) {
0811             p.setBrush(Qt::red);
0812         } else if (selecteds.contains(kfrIx)) {
0813             p.setBrush(Qt::darkRed);
0814         } else if (pos == m_hoverKeyframe) {
0815             p.setBrush(m_colSelected);
0816         } else {
0817             p.setBrush(m_colKeyframe);
0818         }
0819         scaledPos -= m_zoomStart;
0820         scaledPos *= m_zoomFactor;
0821         scaledPos += m_offset;
0822         p.drawLine(QPointF(scaledPos, headOffset), QPointF(scaledPos, m_lineHeight - 1));
0823         switch (keyframe.second.first) {
0824         case KeyframeType::Linear: {
0825             QPolygonF position = QPolygonF() << QPointF(-headOffset / 2.0, headOffset / 2.0) << QPointF(0, 0) << QPointF(headOffset / 2.0, headOffset / 2.0)
0826                                              << QPointF(0, headOffset);
0827             position.translate(scaledPos, 0);
0828             p.drawPolygon(position);
0829             break;
0830         }
0831         case KeyframeType::Discrete:
0832             p.drawRect(QRectF(scaledPos - headOffset / 2.0, 0, headOffset, headOffset));
0833             break;
0834         default:
0835             p.drawEllipse(QRectF(scaledPos - headOffset / 2.0, 0, headOffset, headOffset));
0836             break;
0837         }
0838         kfrIx++;
0839     }
0840 
0841     p.setPen(palette().dark().color());
0842 
0843     /*
0844      * Time-"line"
0845      */
0846     p.setPen(m_colKeyframe);
0847     p.drawLine(m_offset, m_lineHeight, width() - m_offset, m_lineHeight);
0848     p.drawLine(m_offset, m_lineHeight - headOffset / 2, m_offset, m_lineHeight + headOffset / 2);
0849     p.drawLine(width() - m_offset, m_lineHeight - headOffset / 2, width() - m_offset, m_lineHeight + headOffset / 2);
0850 
0851     /*
0852      * current position cursor
0853      */
0854     if (m_position >= 0 && m_position < m_duration) {
0855         double scaledPos = m_position * m_scale;
0856         if (scaledPos >= m_zoomStart && qFloor(scaledPos) <= zoomEnd) {
0857             scaledPos -= m_zoomStart;
0858             scaledPos *= m_zoomFactor;
0859             scaledPos += m_offset;
0860             QPolygon pa(3);
0861             int cursorwidth = int((m_zoomHeight - m_lineHeight) / 1.8);
0862             QPolygonF position = QPolygonF() << QPointF(-cursorwidth, m_zoomHeight - 3) << QPointF(cursorwidth, m_zoomHeight - 3)
0863                                              << QPointF(0, m_lineHeight + 1);
0864             position.translate(scaledPos, 0);
0865             p.setBrush(m_colKeyframe);
0866             p.drawPolygon(position);
0867         }
0868     }
0869     // Rubberband
0870     if (m_clickEnd >= 0) {
0871         int min = int((qMin(m_clickPoint, m_clickEnd) * m_scale - m_zoomStart) * m_zoomFactor + m_offset);
0872         int max = int((qMax(m_clickPoint, m_clickEnd) * m_scale - m_zoomStart) * m_zoomFactor + m_offset);
0873         p.setOpacity(0.5);
0874         p.fillRect(QRect(min, 0, max - min, m_lineHeight), palette().highlight());
0875         p.setOpacity(1);
0876     }
0877 
0878     // Zoom bar
0879     p.setPen(Qt::NoPen);
0880     p.setBrush(palette().mid());
0881     p.drawRoundedRect(m_offset, m_zoomHeight + 3, width() - 2 * m_offset, m_size - m_zoomHeight - 3, m_lineHeight / 5, m_lineHeight / 5);
0882     p.setBrush(palette().highlight());
0883     p.drawRoundedRect(int(m_offset + (width() - m_offset) * m_zoomHandle.x()), m_zoomHeight + 3,
0884                       int((width() - 2 * m_offset) * (m_zoomHandle.y() - m_zoomHandle.x())), m_size - m_zoomHeight - 3, m_lineHeight / 5, m_lineHeight / 5);
0885 }
0886 
0887 void KeyframeView::copyCurrentValue(const QModelIndex &ix, const QString &paramName)
0888 {
0889     int offset = pCore->getItemIn(m_model->getOwnerId());
0890     const QString val = m_model->getInterpolatedValue(m_position + offset, ix).toString();
0891     QString newVal;
0892     const QStringList vals = val.split(QLatin1Char(' '));
0893     qDebug() << "=== COPYING VALS: " << val << " AT POS: " << m_position << ", PARAM NAME_ " << paramName;
0894     auto *parentCommand = new QUndoCommand();
0895     bool multiParams = paramName.contains(QLatin1Char(' '));
0896     for (int &kfrIx : m_model->selectedKeyframes()) {
0897         QString oldValue = m_model->getInterpolatedValue(m_model->getPosAtIndex(kfrIx), ix).toString();
0898         QStringList oldVals = oldValue.split(QLatin1Char(' '));
0899         bool found = false;
0900         if (paramName.contains(QLatin1String("spinX"))) {
0901             oldVals[0] = vals.at(0);
0902             newVal = oldVals.join(QLatin1Char(' '));
0903             found = true;
0904             if (!multiParams) {
0905                 parentCommand->setText(i18n("Update keyframes X position"));
0906             }
0907         }
0908         if (paramName.contains(QLatin1String("spinY"))) {
0909             oldVals[1] = vals.at(1);
0910             newVal = oldVals.join(QLatin1Char(' '));
0911             found = true;
0912             if (!multiParams) {
0913                 parentCommand->setText(i18n("Update keyframes Y position"));
0914             }
0915         }
0916         if (paramName.contains(QLatin1String("spinW"))) {
0917             oldVals[2] = vals.at(2);
0918             newVal = oldVals.join(QLatin1Char(' '));
0919             found = true;
0920             if (!multiParams) {
0921                 parentCommand->setText(i18n("Update keyframes width"));
0922             }
0923         }
0924         if (paramName.contains(QLatin1String("spinH"))) {
0925             oldVals[3] = vals.at(3);
0926             newVal = oldVals.join(QLatin1Char(' '));
0927             found = true;
0928             if (!multiParams) {
0929                 parentCommand->setText(i18n("Update keyframes height"));
0930             }
0931         }
0932         if (paramName.contains(QLatin1String("spinO"))) {
0933             oldVals[4] = vals.at(4);
0934             newVal = oldVals.join(QLatin1Char(' '));
0935             found = true;
0936             if (!multiParams) {
0937                 parentCommand->setText(i18n("Update keyframes opacity"));
0938             }
0939         }
0940         if (!found) {
0941             newVal = val;
0942             parentCommand->setText(i18n("Update keyframes value"));
0943         } else if (multiParams) {
0944             parentCommand->setText(i18n("Update keyframes value"));
0945         }
0946         bool result = m_model->updateKeyframe(m_model->getPosAtIndex(kfrIx), newVal, ix, parentCommand);
0947         if (result) {
0948             pCore->displayMessage(i18n("Keyframe value copied"), InformationMessage);
0949         }
0950     }
0951     pCore->pushUndo(parentCommand);
0952 }