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 ¶mName) 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 }