1 /*
2     SPDX-FileCopyrightText: 2011 Till Theato <root@ttill.de>
3     SPDX-FileCopyrightText: 2017 Nicolas Carion
4     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6 
7 #include "keyframeview.hpp"
8 #include "assets/keyframes/model/keyframemodellist.hpp"
9 #include "core.h"
10 #include "kdenlivesettings.h"
11 
12 #include <QMouseEvent>
13 #include <QApplication>
14 #include <QStylePainter>
15 #include <QtMath>
16 
17 #include <KColorScheme>
18 #include <QFontDatabase>
19 #include <utility>
20 
KeyframeView(std::shared_ptr<KeyframeModelList> model,int duration,QWidget * parent)21 KeyframeView::KeyframeView(std::shared_ptr<KeyframeModelList> model, int duration, QWidget *parent)
22     : QWidget(parent)
23     , m_model(std::move(model))
24     , m_duration(duration)
25     , m_position(0)
26     , m_currentKeyframeOriginal(-1)
27     , m_hoverKeyframe(-1)
28     , m_scale(1)
29     , m_zoomFactor(1)
30     , m_zoomStart(0)
31     , m_moveKeyframeMode(false)
32     , m_keyframeZonePress(false)
33     , m_clickPoint(-1)
34     , m_clickEnd(-1)
35     , m_zoomHandle(0,1)
36     , m_hoverZoomIn(false)
37     , m_hoverZoomOut(false)
38     , m_hoverZoom(false)
39     , m_clickOffset(0)
40 {
41     setMouseTracking(true);
42     setMinimumSize(QSize(150, 20));
43     setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
44     QPalette p = palette();
45     KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window);
46     m_colSelected = palette().highlight().color();
47     m_colKeyframe = scheme.foreground(KColorScheme::NormalText).color();
48     m_size = QFontInfo(font()).pixelSize() * 3;
49     m_lineHeight = int(m_size / 2.1);
50     m_zoomHeight = m_size * 3 / 4;
51     m_offset = m_size / 4;
52     setFixedHeight(m_size);
53     setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
54     connect(m_model.get(), &KeyframeModelList::modelChanged, this, &KeyframeView::slotModelChanged);
55     connect(m_model.get(), &KeyframeModelList::modelDisplayChanged, this, &KeyframeView::slotModelDisplayChanged);
56 }
57 
slotModelChanged()58 void KeyframeView::slotModelChanged()
59 {
60     int offset = pCore->getItemIn(m_model->getOwnerId());
61     emit atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
62     emit modified();
63     update();
64 }
65 
slotModelDisplayChanged()66 void KeyframeView::slotModelDisplayChanged()
67 {
68     int offset = pCore->getItemIn(m_model->getOwnerId());
69     emit atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
70     update();
71 }
72 
slotSetPosition(int pos,bool isInRange)73 void KeyframeView::slotSetPosition(int pos, bool isInRange)
74 {
75     if (!isInRange) {
76         m_position = -1;
77         update();
78         return;
79     }
80     if (pos != m_position) {
81         m_position = pos;
82         int offset = pCore->getItemIn(m_model->getOwnerId());
83         emit atKeyframe(m_model->hasKeyframe(pos + offset), m_model->singleKeyframe());
84         double zoomPos = double(m_position) / m_duration;
85         if (zoomPos < m_zoomHandle.x()) {
86             double interval = m_zoomHandle.y() - m_zoomHandle.x();
87             zoomPos = qBound(0.0, zoomPos - interval / 5, 1.0);
88             m_zoomHandle.setX(zoomPos);
89             m_zoomHandle.setY(zoomPos + interval);
90         } else if (zoomPos > m_zoomHandle.y()) {
91             double interval = m_zoomHandle.y() - m_zoomHandle.x();
92             zoomPos = qBound(0.0, zoomPos + interval / 5, 1.0);
93             m_zoomHandle.setX(zoomPos - interval);
94             m_zoomHandle.setY(zoomPos);
95         }
96         update();
97     }
98 }
99 
initKeyframePos()100 void KeyframeView::initKeyframePos()
101 {
102     emit atKeyframe(m_model->hasKeyframe(m_position), m_model->singleKeyframe());
103 }
104 
slotDuplicateKeyframe()105 void KeyframeView::slotDuplicateKeyframe()
106 {
107     int offset = pCore->getItemIn(m_model->getOwnerId());
108     if (m_model->activeKeyframe() > -1 && !m_model->hasKeyframe(m_position + offset)) {
109         Fun undo = []() { return true; };
110         Fun redo = []() { return true; };
111         int delta = offset + m_position - m_model->getPosAtIndex(m_model->activeKeyframe()).frames(pCore->getCurrentFps());
112         for (int &kf : m_model->selectedKeyframes()) {
113             int kfrPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps());
114             m_model->duplicateKeyframeWithUndo(GenTime(kfrPos, pCore->getCurrentFps()), GenTime(kfrPos + delta, pCore->getCurrentFps()), undo, redo);
115         }
116         pCore->pushUndo(undo, redo, i18n("Duplicate keyframe"));
117     }
118 }
119 
slotAddKeyframe(int pos)120 bool KeyframeView::slotAddKeyframe(int pos)
121 {
122     if (pos < 0) {
123         pos = m_position;
124     }
125     int offset = pCore->getItemIn(m_model->getOwnerId());
126     return m_model->addKeyframe(GenTime(pos + offset, pCore->getCurrentFps()), KeyframeType(KdenliveSettings::defaultkeyframeinterp()));
127 }
128 
getAssetId()129 const QString KeyframeView::getAssetId()
130 {
131     return m_model->getAssetId();
132 }
133 
slotAddRemove()134 void KeyframeView::slotAddRemove()
135 {
136     emit activateEffect();
137     int offset = pCore->getItemIn(m_model->getOwnerId());
138     if (m_model->hasKeyframe(m_position + offset)) {
139         if (m_model->selectedKeyframes().contains(m_position)) {
140             // Delete all selected keyframes
141             slotRemoveKeyframe(m_model->selectedKeyframes());
142         } else {
143             slotRemoveKeyframe({m_position});
144         }
145     } else {
146         if (slotAddKeyframe(m_position)) {
147             int offset = pCore->getItemIn(m_model->getOwnerId());
148             GenTime position(m_position + offset, pCore->getCurrentFps());
149             int currentIx = m_model->getIndexForPos(position);
150             if (currentIx > -1) {
151                 m_model->setSelectedKeyframes({currentIx});
152                 m_model->setActiveKeyframe(currentIx);
153             }
154         }
155     }
156 }
157 
slotEditType(int type,const QPersistentModelIndex & index)158 void KeyframeView::slotEditType(int type, const QPersistentModelIndex &index)
159 {
160     int offset = pCore->getItemIn(m_model->getOwnerId());
161     if (m_model->hasKeyframe(m_position + offset)) {
162         m_model->updateKeyframeType(GenTime(m_position + offset, pCore->getCurrentFps()), type, index);
163     }
164 }
165 
slotRemoveKeyframe(QVector<int> positions)166 void KeyframeView::slotRemoveKeyframe(QVector<int> positions)
167 {
168     if (m_model->singleKeyframe()) {
169         // Don't allow zero keyframe
170         pCore->displayMessage(i18n("Cannot remove the last keyframe"), MessageType::ErrorMessage, 500);
171         return;
172     }
173     int offset = pCore->getItemIn(m_model->getOwnerId());
174     Fun undo = []() { return true; };
175     Fun redo = []() { return true; };
176     for (int pos : positions) {
177         if (pos == 0) {
178             // Don't allow moving first keyframe
179             continue;
180         }
181         m_model->removeKeyframeWithUndo(GenTime(pos + offset, pCore->getCurrentFps()), undo, redo);
182     }
183     pCore->pushUndo(undo, redo, i18np("Remove keyframe", "Remove keyframes", positions.size()));
184 }
185 
setDuration(int dur,int inPoint)186 void KeyframeView::setDuration(int dur, int inPoint)
187 {
188     m_duration = dur;
189     int offset = pCore->getItemIn(m_model->getOwnerId());
190     emit atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
191     // Unselect keyframes that are outside range if any
192     QVector<int> toDelete;
193     int kfrIx = 0;
194     for (auto &p : m_model->selectedKeyframes()) {
195         int kfPos = m_model->getPosAtIndex(p).frames(pCore->getCurrentFps());
196         if (kfPos < offset || kfPos >= offset + m_duration) {
197             toDelete << kfrIx;
198         }
199         kfrIx++;
200     }
201     for (auto &p : toDelete) {
202         m_model->removeFromSelected(p);
203     }
204     update();
205 }
206 
slotGoToNext()207 void KeyframeView::slotGoToNext()
208 {
209     emit activateEffect();
210     if (m_position == m_duration - 1) {
211         return;
212     }
213 
214     bool ok;
215     int offset = pCore->getItemIn(m_model->getOwnerId());
216     auto next = m_model->getNextKeyframe(GenTime(m_position + offset, pCore->getCurrentFps()), &ok);
217 
218     if (ok) {
219         emit seekToPos(qMin(int(next.first.frames(pCore->getCurrentFps())) - offset, m_duration - 1));
220     } else {
221         // no keyframe after current position
222         emit seekToPos(m_duration - 1);
223     }
224 }
225 
slotGoToPrev()226 void KeyframeView::slotGoToPrev()
227 {
228     emit activateEffect();
229     if (m_position == 0) {
230         return;
231     }
232 
233     bool ok;
234     int offset = pCore->getItemIn(m_model->getOwnerId());
235     auto prev = m_model->getPrevKeyframe(GenTime(m_position + offset, pCore->getCurrentFps()), &ok);
236 
237     if (ok) {
238         emit seekToPos(qMax(0, int(prev.first.frames(pCore->getCurrentFps())) - offset));
239     } else {
240         // no keyframe after current position
241         emit seekToPos(m_duration - 1);
242     }
243 }
244 
slotCenterKeyframe()245 void KeyframeView::slotCenterKeyframe()
246 {
247     if (m_currentKeyframeOriginal == -1 || m_currentKeyframeOriginal == m_position || m_currentKeyframeOriginal == 0) {
248         return;
249     }
250     int offset = pCore->getItemIn(m_model->getOwnerId());
251     if (!m_model->hasKeyframe(m_currentKeyframeOriginal)) {
252         return;
253     }
254     Fun undo = []() { return true; };
255     Fun redo = []() { return true; };
256     int delta = m_position - (m_currentKeyframeOriginal - offset);
257     int sourcePosition = m_currentKeyframeOriginal;
258     QVector<int>updatedSelection;
259     for (int &kf : m_model->selectedKeyframes()) {
260         if (kf == 0) {
261             // Don't allow moving first keyframe
262             updatedSelection << 0;
263             continue;
264         }
265         int kfPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps());
266         GenTime initPos(kfPos, pCore->getCurrentFps());
267         GenTime targetPos(kfPos + delta, pCore->getCurrentFps());
268         m_model->moveKeyframeWithUndo(initPos, targetPos, undo, redo);
269         break;
270     }
271     Fun local_redo = [this, position = m_position]() {
272         m_currentKeyframeOriginal = position;
273         update();
274         return true;
275     };
276     Fun local_undo = [this, sourcePosition]() {
277         m_currentKeyframeOriginal = sourcePosition;
278         update();
279         return true;
280     };
281     local_redo();
282     PUSH_LAMBDA(local_redo, redo);
283     PUSH_FRONT_LAMBDA(local_undo, undo);
284     pCore->pushUndo(undo, redo, i18nc("@action", "Move keyframe"));
285 }
286 
mousePressEvent(QMouseEvent * event)287 void KeyframeView::mousePressEvent(QMouseEvent *event)
288 {
289     emit activateEffect();
290     int offset = pCore->getItemIn(m_model->getOwnerId());
291     double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
292     double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
293     double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
294     int pos = int(((event->x() - m_offset) / zoomFactor + zoomStart ) / m_scale);
295     pos = qBound(0, pos, m_duration - 1);
296     m_moveKeyframeMode = false;
297     m_keyframeZonePress = false;
298     if (event->button() == Qt::LeftButton) {
299         if (event->y() < m_lineHeight) {
300             // mouse click in keyframes area
301             bool ok;
302             GenTime position(pos + offset, pCore->getCurrentFps());
303             if (event->modifiers() & Qt::ShiftModifier) {
304                 m_clickPoint = pos;
305                 return;
306             }
307             m_keyframeZonePress = true;
308             auto keyframe = m_model->getClosestKeyframe(position, &ok);
309             if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
310                 int currentIx = m_model->getIndexForPos(keyframe.first);
311                 m_currentKeyframeOriginal = keyframe.first.frames(pCore->getCurrentFps());
312                 if (event->modifiers() & Qt::ControlModifier) {
313                     if (m_model->selectedKeyframes().contains(currentIx)) {
314                         m_model->removeFromSelected(currentIx);
315                         if (m_model->activeKeyframe() == currentIx) {
316                             m_model->setActiveKeyframe(-1);
317                         }
318                         m_currentKeyframeOriginal = -1;
319                     } else {
320                         m_model->appendSelectedKeyframe(currentIx);
321                         m_model->setActiveKeyframe(currentIx);
322                     }
323                 } else if (!m_model->selectedKeyframes().contains(currentIx)) {
324                     m_model->setSelectedKeyframes({currentIx});
325                     m_model->setActiveKeyframe(currentIx);
326                 } else {
327                     m_model->setActiveKeyframe(currentIx);
328                 }
329                 // Select and seek to keyframe
330                 if (m_currentKeyframeOriginal > -1) {
331                     if (KdenliveSettings::keyframeseek()) {
332                         emit seekToPos(m_currentKeyframeOriginal - offset);
333                     }
334                 }
335                 return;
336             }
337             // no keyframe next to mouse
338             m_model->setSelectedKeyframes({});
339             m_model->setActiveKeyframe(-1);
340             m_currentKeyframeOriginal = -1;
341         } else if (event->y() > m_zoomHeight + 2) {
342             // click on zoom area
343             if (m_hoverZoom) {
344                 m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
345             }
346             // When not zoomed, allow seek by clicking on zoombar
347             if (qFuzzyCompare(m_zoomFactor, 1.) && pos != m_position) {
348                 emit seekToPos(pos);
349             }
350             return;
351         }
352     } else if (event->button() == Qt::RightButton && event->y() > m_zoomHeight + 2) {
353         // Right click on zoom, switch between no zoom and last zoom status
354         if (m_zoomHandle == QPointF(0, 1)) {
355             if (!m_lastZoomHandle.isNull()) {
356                 m_zoomHandle = m_lastZoomHandle;
357                 update();
358                 return;
359             }
360         } else {
361             m_lastZoomHandle = m_zoomHandle;
362             m_zoomHandle = QPointF(0, 1);
363             update();
364             return;
365         }
366     }
367     if (pos != m_position) {
368         emit seekToPos(pos);
369         update();
370     }
371 }
372 
mouseMoveEvent(QMouseEvent * event)373 void KeyframeView::mouseMoveEvent(QMouseEvent *event)
374 {
375     int offset = pCore->getItemIn(m_model->getOwnerId());
376     double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
377     double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
378     double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
379     int pos = int(((double(event->x()) - m_offset) / zoomFactor + zoomStart ) / m_scale);
380     pos = qBound(0, pos, m_duration - 1);
381     GenTime position(pos + offset, pCore->getCurrentFps());
382     if ((event->buttons() & Qt::LeftButton) != 0u) {
383         if (m_hoverZoomIn || m_hoverZoomOut || m_hoverZoom) {
384             // Moving zoom handles
385             if (m_hoverZoomIn) {
386                 m_zoomHandle.setX(qMin(qMax(0., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.y() - 0.015));
387                 update();
388                 return;
389             }
390             if (m_hoverZoomOut) {
391                 m_zoomHandle.setY(qMax(qMin(1., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.x() + 0.015));
392                 update();
393                 return;
394             }
395             // moving zoom zone
396             if (m_hoverZoom) {
397                 double clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset) - m_clickOffset;
398                 double newX = m_zoomHandle.x() + clickOffset;
399                 if (newX < 0) {
400                     clickOffset = - m_zoomHandle.x();
401                     newX = 0;
402                 }
403                 double newY = m_zoomHandle.y() + clickOffset;
404                 if (newY > 1) {
405                     clickOffset = 1 - m_zoomHandle.y();
406                     newY = 1;
407                     newX = m_zoomHandle.x() + clickOffset;
408                 }
409                 m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
410                 m_zoomHandle = QPointF(newX, newY);
411                 update();
412             }
413             return;
414         }
415         if (m_model->activeKeyframe() == pos) {
416             return;
417         }
418         if (m_model->activeKeyframe() > 0 && m_currentKeyframeOriginal > -1 && m_clickPoint == -1 && (m_moveKeyframeMode || (qAbs(pos - (m_currentKeyframeOriginal - offset)) * m_scale * m_zoomFactor < QApplication::startDragDistance() && m_keyframeZonePress))) {
419             m_moveKeyframeMode = true;
420             if (!m_model->hasKeyframe(pos + offset)) {
421                 int delta = pos - (m_model->getPosAtIndex(m_model->activeKeyframe()).frames(pCore->getCurrentFps()) - offset);
422                 // Check that the move is possible
423                 for (int &kf : m_model->selectedKeyframes()) {
424                     int updatedPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps()) + delta;
425                     if (m_model->hasKeyframe(updatedPos)) {
426                         // Don't allow moving over another keyframe
427                         return;
428                     }
429                 }
430                 for (int &kf : m_model->selectedKeyframes()) {
431                     if (kf == 0) {
432                         // Don't allow moving first keyframe
433                         continue;
434                     }
435                     int kfPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps());
436                     GenTime currentPos(kfPos, pCore->getCurrentFps());
437                     GenTime updatedPos(kfPos + delta, pCore->getCurrentFps());
438                     if (!m_model->moveKeyframe(currentPos, updatedPos, false)) {
439                         qDebug()<<"=== FAILED KF MOVE!!!";
440                         Q_ASSERT(false);
441                     }
442                     // We only move first keyframe, the other are moved in the model command
443                     break;
444                 }
445             }
446         }
447         // Rubberband selection
448         if (m_clickPoint >= 0) {
449             m_clickEnd = pos;
450             int min = qMin(m_clickPoint, m_clickEnd);
451             int max = qMax(m_clickPoint, m_clickEnd);
452             min = qMax(1, min);
453             m_model->setSelectedKeyframes({});
454             m_model->setActiveKeyframe(-1);
455             m_currentKeyframeOriginal = -1;
456             double fps = pCore->getCurrentFps();
457             int kfrIx = 0;
458             for (const auto &keyframe : *m_model.get()) {
459                 int pos = keyframe.first.frames(fps) - offset;
460                 if (pos > min && pos <= max) {
461                     m_model->appendSelectedKeyframe(kfrIx);
462                 }
463                 kfrIx++;
464             }
465             if (!m_model->selectedKeyframes().isEmpty()) {
466                 m_model->setActiveKeyframe(m_model->selectedKeyframes().first());
467                 m_currentKeyframeOriginal = m_model->getPosAtIndex(m_model->selectedKeyframes().first()).frames(pCore->getCurrentFps());
468             }
469             update();
470             return;
471         }
472 
473         if (!m_moveKeyframeMode || KdenliveSettings::keyframeseek()) {
474             if (pos != m_position) {
475                 emit seekToPos(pos);
476             }
477         }
478         return;
479     }
480     if (event->y() < m_lineHeight) {
481         bool ok;
482         auto keyframe = m_model->getClosestKeyframe(position, &ok);
483         if (ok && qAbs(((position.frames(pCore->getCurrentFps()) - keyframe.first.frames(pCore->getCurrentFps())) * m_scale) * m_zoomFactor) < QApplication::startDragDistance()) {
484             m_hoverKeyframe = keyframe.first.frames(pCore->getCurrentFps()) - offset;
485             setCursor(Qt::PointingHandCursor);
486             m_hoverZoomIn = false;
487             m_hoverZoomOut = false;
488             m_hoverZoom = false;
489             update();
490             return;
491         }
492     }
493     if (event->y() > m_zoomHeight + 2) {
494         // Moving in zoom area
495         if (qAbs(event->x() - m_offset - (m_zoomHandle.x() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
496             setCursor(Qt::SizeHorCursor);
497             m_hoverZoomIn = true;
498             m_hoverZoomOut = false;
499             m_hoverZoom = false;
500             update();
501             return;
502         }
503         if (qAbs(event->x() - m_offset - (m_zoomHandle.y() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
504             setCursor(Qt::SizeHorCursor);
505             m_hoverZoomOut = true;
506             m_hoverZoomIn = false;
507             m_hoverZoom = false;
508             update();
509             return;
510         }
511         if (m_zoomHandle != QPointF(0, 1) && event->x() > m_offset + (m_zoomHandle.x() * (width() - 2 * m_offset)) && event->x() < m_offset + (m_zoomHandle.y() * (width() - 2 * m_offset))) {
512             setCursor(Qt::PointingHandCursor);
513             m_hoverZoom = true;
514             m_hoverZoomIn = false;
515             m_hoverZoomOut = false;
516             update();
517             return;
518         }
519     }
520 
521     if (m_hoverKeyframe != -1 || m_hoverZoomOut || m_hoverZoomIn || m_hoverZoom) {
522         m_hoverKeyframe = -1;
523         m_hoverZoomOut = false;
524         m_hoverZoomIn = false;
525         m_hoverZoom = false;
526         setCursor(Qt::ArrowCursor);
527         update();
528     }
529 }
530 
mouseReleaseEvent(QMouseEvent * event)531 void KeyframeView::mouseReleaseEvent(QMouseEvent *event)
532 {
533     Q_UNUSED(event)
534     m_clickPoint = -1;
535     if (m_clickEnd >= 0) {
536         m_clickEnd = -1;
537         update();
538     }
539     if (m_moveKeyframeMode && m_model->activeKeyframe() >= 0 && m_currentKeyframeOriginal != m_model->getPosAtIndex(m_model->activeKeyframe()).frames(pCore->getCurrentFps())) {
540         int delta = m_model->getPosAtIndex(m_model->activeKeyframe()).frames(pCore->getCurrentFps()) - m_currentKeyframeOriginal;
541         // Move back all keyframes to their initial positions
542         for (int &kf : m_model->selectedKeyframes()) {
543             if (kf == 0) {
544                 // Don't allow moving first keyframe
545                 continue;
546             }
547             int kfPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps());
548             GenTime initPos(kfPos - delta, pCore->getCurrentFps());
549             GenTime targetPos(kfPos, pCore->getCurrentFps());
550             m_model->moveKeyframe(targetPos, initPos, false, true);
551             break;
552         }
553         // Move all keyframes to their new positions
554         Fun undo = []() { return true; };
555         Fun redo = []() { return true; };
556         for (int &kf : m_model->selectedKeyframes()) {
557             if (kf == 0) {
558                 // Don't allow moving first keyframe
559                 continue;
560             }
561             int kfPos = m_model->getPosAtIndex(kf).frames(pCore->getCurrentFps());
562             GenTime initPos(kfPos, pCore->getCurrentFps());
563             GenTime targetPos(kfPos + delta, pCore->getCurrentFps());
564             m_model->moveKeyframeWithUndo(initPos, targetPos, undo, redo);
565             break;
566         }
567         m_currentKeyframeOriginal = m_model->getPosAtIndex(m_model->activeKeyframe()).frames(pCore->getCurrentFps());
568         pCore->pushUndo(undo, redo, i18np("Move keyframe", "Move keyframes", m_model->selectedKeyframes().size()));
569         qDebug() << "RELEASING keyframe move" << delta;
570     }
571     m_moveKeyframeMode = false;
572     m_keyframeZonePress = false;
573 }
574 
mouseDoubleClickEvent(QMouseEvent * event)575 void KeyframeView::mouseDoubleClickEvent(QMouseEvent *event)
576 {
577     if (event->button() == Qt::LeftButton && event->y() < m_lineHeight) {
578         int offset = pCore->getItemIn(m_model->getOwnerId());
579         double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
580         double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
581         double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
582         int pos = int(((event->x() - m_offset) / zoomFactor + zoomStart ) / m_scale);
583         pos = qBound(0, pos, m_duration - 1);
584         GenTime position(pos + offset, pCore->getCurrentFps());
585         bool ok;
586         auto keyframe = m_model->getClosestKeyframe(position, &ok);
587         if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset)* m_scale * m_zoomFactor < QApplication::startDragDistance()) {
588             if (keyframe.first.frames(pCore->getCurrentFps()) != offset) {
589                 m_model->removeKeyframe(keyframe.first);
590                 m_currentKeyframeOriginal = -1;
591                 if (keyframe.first.frames(pCore->getCurrentFps()) == m_position + offset) {
592                     emit atKeyframe(false, m_model->singleKeyframe());
593                 }
594             }
595             return;
596         }
597 
598         // add new keyframe
599         m_model->addKeyframe(position, KeyframeType(KdenliveSettings::defaultkeyframeinterp()));
600     } else {
601         QWidget::mouseDoubleClickEvent(event);
602     }
603 }
604 
wheelEvent(QWheelEvent * event)605 void KeyframeView::wheelEvent(QWheelEvent *event)
606 {
607     if (event->modifiers() & Qt::AltModifier) {
608         // Alt modifier seems to invert x/y axis
609         if (event->angleDelta().x() > 0) {
610             slotGoToPrev();
611         } else {
612             slotGoToNext();
613         }
614         return;
615     }
616     if (event->modifiers() & Qt::ControlModifier) {
617         int maxWidth = width() - 2 * m_offset;
618         m_zoomStart = m_zoomHandle.x() * maxWidth;
619         m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
620         double scaledPos = m_position * m_scale;
621         double zoomRange = (m_zoomHandle.y() - m_zoomHandle.x()) * maxWidth;
622         if (event->angleDelta().y() > 0) {
623             zoomRange /= 1.5;
624         } else {
625             zoomRange *= 1.5;
626         }
627         if (zoomRange < 5) {
628             // Don't allow too small zoombar
629             return;
630         }
631         double length = (scaledPos - zoomRange / 2) / maxWidth;
632         m_zoomHandle.setX(qMax(0., length));
633         if (length < 0) {
634             m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth - length));
635         } else {
636             m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth));
637         }
638         update();
639         return;
640     }
641     int change = event->angleDelta().y() > 0 ? -1 : 1;
642     int pos = qBound(0, m_position + change, m_duration - 1);
643     emit seekToPos(pos);
644 }
645 
paintEvent(QPaintEvent * event)646 void KeyframeView::paintEvent(QPaintEvent *event)
647 {
648     Q_UNUSED(event)
649 
650     QStylePainter p(this);
651     int maxWidth = width() - 2 * m_offset;
652     if (m_duration > 1) {
653         m_scale = maxWidth / double(m_duration - 1);
654     } else {
655         m_scale = maxWidth;
656     }
657     int headOffset = m_lineHeight / 2;
658     int offset = pCore->getItemIn(m_model->getOwnerId());
659     m_zoomStart = m_zoomHandle.x() * maxWidth;
660     m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
661     int zoomEnd = qCeil(m_zoomHandle.y() * maxWidth);
662     /* ticks */
663     double fps = pCore->getCurrentFps();
664     int displayedLength = int(m_duration / m_zoomFactor / fps);
665     double factor = 1;
666     if (displayedLength < 2) {
667         // 1 frame tick
668     } else if (displayedLength < 30 ) {
669         // 1 sec tick
670         factor = fps;
671     } else if (displayedLength < 150) {
672         // 5 sec tick
673         factor = 5 * fps;
674     } else if (displayedLength < 300) {
675         // 10 sec tick
676         factor = 10 * fps;
677     } else if (displayedLength < 900) {
678         // 30 sec tick
679         factor = 30 * fps;
680     } else if (displayedLength < 1800) {
681         // 1 min. tick
682         factor = 60 * fps;
683     } else if (displayedLength < 9000) {
684         // 5 min tick
685         factor = 300 * fps;
686     } else if (displayedLength < 18000) {
687         // 10 min tick
688         factor = 600 * fps;
689     } else {
690         // 30 min tick
691         factor = 1800 * fps;
692     }
693 
694     // Position of left border in frames
695     double tickOffset = m_zoomStart * m_zoomFactor;
696     double frameSize = factor * m_scale * m_zoomFactor;
697     int base = int(tickOffset / frameSize);
698     tickOffset = frameSize - (tickOffset - (base * frameSize));
699     // Draw frame ticks
700     int scaledTick = 0;
701     for (int i = 0; i < maxWidth / frameSize; i++) {
702         scaledTick = int(m_offset + (i * frameSize) + tickOffset);
703         if (scaledTick >= maxWidth + m_offset) {
704             break;
705         }
706         p.drawLine(QPointF(scaledTick , m_lineHeight + 1), QPointF(scaledTick, m_lineHeight - 3));
707     }
708 
709 
710     /*
711      * keyframes
712      */
713     int kfrIx = 0;
714     QVector<int> selecteds = m_model->selectedKeyframes();
715     for (const auto &keyframe : *m_model.get()) {
716         int pos = keyframe.first.frames(fps) - offset;
717         if (pos < 0) continue;
718         double scaledPos = pos * m_scale;
719         if (scaledPos < m_zoomStart || qFloor(scaledPos) > zoomEnd) {
720             kfrIx++;
721             continue;
722         }
723         if (kfrIx == m_model->activeKeyframe()) {
724             p.setBrush(Qt::red);
725         } else if (selecteds.contains(kfrIx)) {
726             p.setBrush(Qt::darkRed);
727         } else if (pos == m_hoverKeyframe) {
728             p.setBrush(m_colSelected);
729         } else {
730             p.setBrush(m_colKeyframe);
731         }
732         scaledPos -= m_zoomStart;
733         scaledPos *= m_zoomFactor;
734         scaledPos += m_offset;
735         p.drawLine(QPointF(scaledPos, headOffset), QPointF(scaledPos, m_lineHeight - 1));
736         switch (keyframe.second.first) {
737         case KeyframeType::Linear: {
738             QPolygonF position = QPolygonF() << QPointF(-headOffset / 2.0, headOffset / 2.0) << QPointF(0, 0) << QPointF(headOffset / 2.0, headOffset / 2.0)
739                                              << QPointF(0, headOffset);
740             position.translate(scaledPos, 0);
741             p.drawPolygon(position);
742             break;
743         }
744         case KeyframeType::Discrete:
745             p.drawRect(QRectF(scaledPos - headOffset / 2.0, 0, headOffset, headOffset));
746             break;
747         default:
748             p.drawEllipse(QRectF(scaledPos - headOffset / 2.0, 0, headOffset, headOffset));
749             break;
750         }
751         kfrIx++;
752     }
753 
754     p.setPen(palette().dark().color());
755 
756     /*
757      * Time-"line"
758      */
759     p.setPen(m_colKeyframe);
760     p.drawLine(m_offset, m_lineHeight, width() - m_offset, m_lineHeight);
761     p.drawLine(m_offset, m_lineHeight - headOffset / 2, m_offset, m_lineHeight + headOffset / 2);
762     p.drawLine(width() - m_offset, m_lineHeight - headOffset / 2, width() - m_offset, m_lineHeight + headOffset / 2);
763 
764     /*
765      * current position cursor
766      */
767     if (m_position >= 0 && m_position < m_duration) {
768         double scaledPos = m_position * m_scale;
769         if (scaledPos >= m_zoomStart && qFloor(scaledPos) <= zoomEnd) {
770             scaledPos -= m_zoomStart;
771             scaledPos *= m_zoomFactor;
772             scaledPos += m_offset;
773             QPolygon pa(3);
774             int cursorwidth = int((m_zoomHeight - m_lineHeight) / 1.8);
775             QPolygonF position = QPolygonF() << QPointF(-cursorwidth, m_zoomHeight - 3) << QPointF(cursorwidth, m_zoomHeight - 3) << QPointF(0, m_lineHeight + 1);
776             position.translate(scaledPos, 0);
777             p.setBrush(m_colKeyframe);
778             p.drawPolygon(position);
779         }
780     }
781     // Rubberband
782     if (m_clickEnd >= 0) {
783         int min = int((qMin(m_clickPoint, m_clickEnd) * m_scale - m_zoomStart) * m_zoomFactor + m_offset);
784         int max = int((qMax(m_clickPoint, m_clickEnd) * m_scale - m_zoomStart) * m_zoomFactor + m_offset);
785         p.setOpacity(0.5);
786         p.fillRect(QRect(min, 0, max - min, m_lineHeight), palette().highlight());
787         p.setOpacity(1);
788     }
789 
790     // Zoom bar
791     p.setPen(Qt::NoPen);
792     p.setBrush(palette().mid());
793     p.drawRoundedRect(m_offset, m_zoomHeight + 3, width() - 2 * m_offset, m_size - m_zoomHeight - 3, m_lineHeight / 5, m_lineHeight / 5);
794     p.setBrush(palette().highlight());
795     p.drawRoundedRect(int(m_offset + (width() - m_offset) * m_zoomHandle.x()),
796                       m_zoomHeight + 3,
797                       int((width() - 2 * m_offset) * (m_zoomHandle.y() - m_zoomHandle.x())),
798                       m_size - m_zoomHeight - 3,
799                       m_lineHeight / 5, m_lineHeight / 5);
800 }
801 
802 
copyCurrentValue(QModelIndex ix,const QString paramName)803 void KeyframeView::copyCurrentValue(QModelIndex ix, const  QString paramName)
804 {
805     int offset = pCore->getItemIn(m_model->getOwnerId());
806     const QString val = m_model->getInterpolatedValue(m_position + offset, ix).toString();
807     QString newVal;
808     const QStringList vals = val.split(QLatin1Char(' '));
809     qDebug()<<"=== COPYING VALS: "<<val<<" AT POS: "<<m_position<<", PARAM NAME_ "<<paramName;
810     auto *parentCommand = new QUndoCommand();
811     bool multiParams = paramName.contains(QLatin1Char(' '));
812     for (int &kfrIx : m_model->selectedKeyframes()) {
813         QString oldValue = m_model->getInterpolatedValue(m_model->getPosAtIndex(kfrIx), ix).toString();
814         QStringList oldVals = oldValue.split(QLatin1Char(' '));
815         bool found = false;
816         if (paramName.contains(QLatin1String("spinX"))) {
817             oldVals[0] = vals.at(0);
818             newVal = oldVals.join(QLatin1Char(' '));
819             found = true;
820             if (!multiParams) {
821                 parentCommand->setText(i18n("Update keyframes X position"));
822             }
823         }
824         if (paramName.contains(QLatin1String("spinY"))) {
825             oldVals[1] = vals.at(1);
826             newVal = oldVals.join(QLatin1Char(' '));
827             found = true;
828             if (!multiParams) {
829                 parentCommand->setText(i18n("Update keyframes Y position"));
830             }
831         }
832         if (paramName.contains(QLatin1String("spinW"))) {
833             oldVals[2] = vals.at(2);
834             newVal = oldVals.join(QLatin1Char(' '));
835             found = true;
836             if (!multiParams) {
837                 parentCommand->setText(i18n("Update keyframes width"));
838             }
839         }
840         if (paramName.contains(QLatin1String("spinH"))) {
841             oldVals[3] = vals.at(3);
842             newVal = oldVals.join(QLatin1Char(' '));
843             found = true;
844             if (!multiParams) {
845                 parentCommand->setText(i18n("Update keyframes height"));
846             }
847         }
848         if (paramName.contains(QLatin1String("spinO"))) {
849             oldVals[4] = vals.at(4);
850             newVal = oldVals.join(QLatin1Char(' '));
851             found = true;
852             if (!multiParams) {
853                 parentCommand->setText(i18n("Update keyframes opacity"));
854             }
855         }
856         if (!found) {
857             newVal = val;
858             parentCommand->setText(i18n("Update keyframes value"));
859         } else if (multiParams) {
860             parentCommand->setText(i18n("Update keyframes value"));
861         }
862         bool result = m_model->updateKeyframe(m_model->getPosAtIndex(kfrIx), newVal, ix, parentCommand);
863         if (result) {
864             pCore->displayMessage(i18n("Keyframe value copied"), InformationMessage);
865         }
866     }
867     pCore->pushUndo(parentCommand);
868 }
869