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