1 /*
2 SPDX-FileCopyrightText: 2021 Jean-Baptiste Mardelle <jb@kdenlive.org>
3
4 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6
7 #include "timeremap.h"
8
9 #include "core.h"
10 #include "doc/kthumb.h"
11 #include "timecodedisplay.h"
12 #include "kdenlivesettings.h"
13 #include "bin/projectclip.h"
14 #include "project/projectmanager.h"
15 #include "monitor/monitor.h"
16 #include "profiles/profilemodel.hpp"
17 #include "mainwindow.h"
18 #include "timeline2/view/timelinewidget.h"
19 #include "timeline2/view/timelinecontroller.h"
20 #include "timeline2/model/groupsmodel.hpp"
21 #include "timeline2/model/clipmodel.hpp"
22 #include "macros.hpp"
23
24 #include "kdenlive_debug.h"
25 #include <QFontDatabase>
26 #include <QWheelEvent>
27 #include <QStylePainter>
28 #include <QtMath>
29
30 #include <KColorScheme>
31 #include "klocalizedstring.h"
32
RemapView(QWidget * parent)33 RemapView::RemapView(QWidget *parent)
34 : QWidget(parent)
35 , m_inFrame(0)
36 , m_duration(1)
37 , m_position(0)
38 , m_bottomPosition(0)
39 , m_scale(1.)
40 , m_zoomFactor(1)
41 , m_zoomStart(0)
42 , m_zoomHandle(0,1)
43 , m_clip(nullptr)
44 , m_service(nullptr)
45 , m_moveKeyframeMode(NoMove)
46 , m_clickPoint(-1)
47 , m_moveNext(true)
48 {
49 setMouseTracking(true);
50 setMinimumSize(QSize(150, 80));
51 setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
52 int size = QFontInfo(font()).pixelSize() * 3;
53 setFixedHeight(size * 4);
54 // Reference height of the rulers
55 m_lineHeight = int(size / 2.);
56 // Height of the zoom bar
57 m_zoomHeight = m_lineHeight * 0.5;
58 // Center of the view
59 m_centerPos = (size * 4 - m_zoomHeight - 2) / 2 - 1;
60 m_offset = qCeil(m_lineHeight / 4);
61 // Bottom of the view (just above zoombar)
62 m_bottomView = height() - m_zoomHeight - 2;
63 setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
64 int maxWidth = width() - (2 * m_offset);
65 m_scale = 1.;
66 m_zoomStart = m_zoomHandle.x() * maxWidth;
67 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
68 timer.setInterval(500);
69 timer.setSingleShot(true);
70 connect(&timer, &QTimer::timeout, this, &RemapView::reloadProducer);
71 }
72
isInRange() const73 bool RemapView::isInRange() const
74 {
75 return m_bottomPosition != -1;
76 }
77
updateInPos(int pos)78 void RemapView::updateInPos(int pos)
79 {
80 if (m_currentKeyframe.second > -1) {
81 if (m_moveNext) {
82 int offset = pos - m_currentKeyframe.second;
83 QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first);
84 while (it != m_keyframes.end()) {
85 m_keyframes.insert(it.key(), it.value() + offset);
86 it++;
87 }
88 m_currentKeyframe.second = pos;
89 } else {
90 m_currentKeyframe.second = pos;
91 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
92 }
93 slotSetPosition(pos);
94 std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
95 emit updateSpeeds(speeds);
96 emit updateKeyframes(true);
97 update();
98 }
99 }
100
updateOutPos(int pos)101 void RemapView::updateOutPos(int pos)
102 {
103 if (m_currentKeyframe.first > -1) {
104 if (m_keyframes.contains(pos)) {
105 // Cannot move kfr over an existing one
106 qDebug()<<"==== KEYFRAME ALREADY EXISTS AT: "<<pos;
107 return;
108 }
109 QMap<int,int>updated;
110 int offset = pos - m_currentKeyframe.first;
111 if (m_moveNext) {
112 QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first);
113 while (it != m_keyframes.end()) {
114 updated.insert(it.key(), it.value());
115 it++;
116 }
117 m_currentKeyframe.first = pos;
118 } else {
119 m_keyframes.remove(m_currentKeyframe.first);
120 m_currentKeyframe.first = pos;
121 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
122 }
123 m_selectedKeyframes = {m_currentKeyframe};
124 QMapIterator<int, int> i(updated);
125 while (i.hasNext()) {
126 i.next();
127 m_keyframes.remove(i.key());
128 }
129 i.toFront();
130 while (i.hasNext()) {
131 i.next();
132 m_keyframes.insert(i.key() + offset, i.value());
133 }
134 m_bottomPosition = pos - m_inFrame;
135 std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
136 emit updateSpeeds(speeds);
137 emit updateKeyframes(true);
138 update();
139 }
140 }
141
remapDuration() const142 int RemapView::remapDuration() const
143 {
144 int maxDuration = 0;
145 if (m_keyframes.isEmpty()) {
146 return 0;
147 }
148 QMapIterator<int, int> i(m_keyframes);
149 while (i.hasNext()) {
150 i.next();
151 if (i.key() > maxDuration) {
152 maxDuration = i.key();
153 }
154 }
155 return maxDuration - m_inFrame + 1;
156 }
157
remapMax() const158 int RemapView::remapMax() const
159 {
160 int maxDuration = 0;
161 if (m_keyframes.isEmpty()) {
162 return 0;
163 }
164 QMapIterator<int, int> i(m_keyframes);
165 while (i.hasNext()) {
166 i.next();
167 if (i.value() > maxDuration) {
168 maxDuration = i.value();
169 }
170 if (i.key() > maxDuration) {
171 maxDuration = i.key();
172 }
173 }
174 return maxDuration - m_inFrame + 1;
175 }
176
movingKeyframe() const177 bool RemapView::movingKeyframe() const
178 {
179 return m_moveKeyframeMode == BottomMove;
180 }
181
setBinClipDuration(std::shared_ptr<ProjectClip> clip,int duration)182 void RemapView::setBinClipDuration(std::shared_ptr<ProjectClip> clip, int duration)
183 {
184 m_clip = clip;
185 m_service = clip->originalProducer();
186 m_duration = duration;
187 int maxWidth = width() - (2 * m_offset);
188 m_scale = maxWidth / double(qMax(1, remapMax()));
189 m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
190 }
191
setDuration(std::shared_ptr<Mlt::Producer> service,int duration,int sourceDuration)192 void RemapView::setDuration(std::shared_ptr<Mlt::Producer> service, int duration, int sourceDuration)
193 {
194 m_clip = nullptr;
195 m_sourceDuration = sourceDuration;
196 if (duration < 0) {
197 // reset
198 m_service = nullptr;
199 m_inFrame = 0;
200 m_duration = -1;
201 m_selectedKeyframes.clear();
202 m_keyframes.clear();
203 return;
204 }
205 if (service) {
206 m_service = service;
207 m_inFrame = 0;
208 m_duration = -1;
209 }
210 bool keyframeAdded = false;
211 if (m_duration > 0 && m_service && !m_keyframes.isEmpty()) {
212 m_keyframesOrigin = m_keyframes;
213 if (duration > m_duration) {
214 // The clip was resized, ensure we have a keyframe at the end of the clip will freeze at last keyframe
215 QMap<int, int>::const_iterator it = m_keyframes.constEnd();
216 it--;
217 int lastKeyframePos = it.key();
218 int lastKeyframeValue = it.value();
219 int lastPos = m_inFrame + duration - 1;
220 if (lastPos > lastKeyframePos) {
221 keyframeAdded = true;
222 std::pair<double,double>speeds = getSpeed({lastKeyframePos,lastKeyframeValue});
223 // Move last keyframe
224 it--;
225 int updatedVal = it.value() + ((lastPos - it.key()) * speeds.first);
226 m_keyframes.remove(lastKeyframePos);
227 m_keyframes.insert(lastPos, updatedVal);
228 }
229 } else if (duration < m_duration) {
230 keyframeAdded = true;
231 // Remove all Keyframes after duration
232 int lastPos = m_inFrame + duration - 1;
233 QList <int> toDelete;
234 QMapIterator<int, int> i(m_keyframes);
235 while (i.hasNext()) {
236 i.next();
237 if (i.key() > duration + m_inFrame) {
238 toDelete << i.key();
239 }
240 }
241 if (!toDelete.isEmpty()) {
242 // Move last keyframe to end pos
243 int posToMove = toDelete.takeFirst();
244 if (!m_keyframes.contains(lastPos)) {
245 int lastKeyframeValue = m_keyframes.value(posToMove);
246 std::pair<double,double>speeds = getSpeed({posToMove,lastKeyframeValue});
247 m_keyframes.remove(posToMove);
248 while (!toDelete.isEmpty()) {
249 m_keyframes.remove(toDelete.takeFirst());
250 }
251 int updatedVal = m_keyframes.value(m_keyframes.lastKey()) + ((lastPos - m_keyframes.lastKey()) / speeds.first);
252 m_keyframes.insert(lastPos, updatedVal);
253 }
254 } else {
255 while (!toDelete.isEmpty()) {
256 m_keyframes.remove(toDelete.takeFirst());
257 }
258 }
259 }
260 if (m_keyframes != m_keyframesOrigin) {
261 emit updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin);
262 }
263 }
264 if (service == nullptr) {
265 // We are updating an existing remap effect due to duration change
266 m_duration = qMax(duration, remapMax());
267 } else {
268 m_duration = duration;
269 m_inFrame = m_service->get_in();
270 m_originalRange = {m_inFrame, duration + m_inFrame};
271 }
272 int maxWidth = width() - (2 * m_offset);
273 m_scale = maxWidth / double(qMax(1, remapMax() - 1));
274 m_zoomStart = m_zoomHandle.x() * maxWidth;
275 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
276 if (!m_keyframes.contains(m_currentKeyframe.first)) {
277 m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
278 update();
279 }
280 if (keyframeAdded) {
281 emit updateKeyframes(false);
282 }
283 }
284
loadKeyframes(const QString & mapData)285 void RemapView::loadKeyframes(const QString &mapData)
286 {
287 m_keyframes.clear();
288 if (mapData.isEmpty()) {
289 if (m_inFrame > 0) {
290 // Necessary otherwise clip will freeze before first keyframe
291 m_keyframes.insert(0, 0);
292 }
293 m_currentKeyframe = {m_inFrame, m_inFrame};
294 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
295 m_keyframes.insert(m_inFrame + m_duration - 1, m_inFrame + m_duration - 1);
296 std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
297 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
298 emit selectedKf(m_currentKeyframe, speeds, atEnd);
299 emit atKeyframe(true, true);
300 emit updateKeyframes(false);
301 } else {
302 QStringList str = mapData.split(QLatin1Char(';'));
303 for (auto &s : str) {
304 int pos = m_service->time_to_frames(s.section(QLatin1Char('='), 0, 0).toUtf8().constData());
305 int val = GenTime(s.section(QLatin1Char('='), 1).toDouble()).frames(pCore->getCurrentFps());
306 // HACK: we always set last keyframe 1 frame after in MLT to ensure we have a correct last frame
307 if (s == str.constLast()) {
308 pos--;
309 }
310 m_keyframes.insert(pos, val);
311 m_duration = qMax(m_duration, pos - m_inFrame);
312 m_duration = qMax(m_duration, val - m_inFrame);
313 }
314 bool isKfr = m_keyframes.contains(m_bottomPosition + m_inFrame);
315 if (isKfr) {
316 bool isLast = m_bottomPosition + m_inFrame == m_keyframes.firstKey() || m_bottomPosition + m_inFrame == m_keyframes.lastKey();
317 emit atKeyframe(isKfr, isLast);
318 } else {
319 emit atKeyframe(false, false);
320 }
321 if (m_keyframes.contains(m_currentKeyframe.first)) {
322 //bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey();
323 //emit atKeyframe(true, isLast);
324 std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
325 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
326 emit selectedKf(m_currentKeyframe, speeds, atEnd);
327 } else {
328 m_currentKeyframe = {-1,-1};
329 emit selectedKf(m_currentKeyframe, {-1,-1} );
330 }
331 }
332 int maxWidth = width() - (2 * m_offset);
333 m_scale = maxWidth / double(qMax(1, remapMax()));
334 m_zoomStart = m_zoomHandle.x() * maxWidth;
335 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
336 emit updateMaxDuration();
337 update();
338 }
339
mouseMoveEvent(QMouseEvent * event)340 void RemapView::mouseMoveEvent(QMouseEvent *event)
341 {
342 event->accept();
343 // Check for start/end snapping on drag
344 int pos = -1;
345 if (m_moveKeyframeMode == BottomMove || m_moveKeyframeMode == TopMove) {
346 double snapPos = ((m_originalRange.first - m_inFrame) * m_scale) - m_zoomStart;
347 snapPos *= m_zoomFactor;
348 snapPos += m_offset;
349 if (qAbs(snapPos - event->x()) < QApplication::startDragDistance()) {
350 pos = m_originalRange.first - m_inFrame;
351 } else {
352 snapPos = ((m_originalRange.second - m_inFrame) * m_scale) - m_zoomStart;
353 snapPos *= m_zoomFactor;
354 snapPos += m_offset;
355 if (qAbs(snapPos - event->x()) < QApplication::startDragDistance()) {
356 pos = m_originalRange.second - m_inFrame;
357 }
358 }
359 if (pos == -1) {
360 pos = int(((event->x() - m_offset) / m_zoomFactor + m_zoomStart ) / m_scale);
361 }
362 } else {
363 pos = int(((event->x() - m_offset) / m_zoomFactor + m_zoomStart ) / m_scale);
364 }
365 pos = qBound(0, pos, m_maxLength - 1);
366 GenTime position(pos, pCore->getCurrentFps());
367 if (event->buttons() == Qt::NoButton) {
368 bool hoverKeyframe = false;
369 if (event->y() < 2 * m_lineHeight && event->y() > m_lineHeight) {
370 // mouse move in top keyframes area
371 std::pair<int,int> keyframe = getClosestKeyframe(pos + m_inFrame);
372 if (keyframe.first > -1 && qAbs(keyframe.second - pos - m_inFrame) * m_scale * m_zoomFactor <= m_lineHeight / 2) {
373 hoverKeyframe = true;
374 }
375 } else if (event->y() > m_bottomView - 2 * m_lineHeight && event->y() < m_bottomView - m_lineHeight) {
376 // move in bottom keyframe area
377 std::pair<int,int> keyframe = getClosestKeyframe(pos + m_inFrame, true);
378 if (keyframe.first > -1 && qAbs(keyframe.first - pos - m_inFrame) * m_scale * m_zoomFactor <= m_lineHeight / 2) {
379 hoverKeyframe = true;
380 }
381 }
382 if (hoverKeyframe) {
383 setCursor(Qt::PointingHandCursor);
384 } else {
385 setCursor(Qt::ArrowCursor);
386 }
387 } else if ((event->buttons() & Qt::LeftButton) != 0u) {
388 if (m_hoverZoomIn || m_hoverZoomOut || m_hoverZoom) {
389 // Moving zoom handles
390 if (m_hoverZoomIn) {
391 m_zoomHandle.setX(qMin(qMax(0., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.y() - 0.015));
392 int maxWidth = width() - (2 * m_offset);
393 m_zoomStart = m_zoomHandle.x() * maxWidth;
394 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
395 update();
396 return;
397 }
398 if (m_hoverZoomOut) {
399 m_zoomHandle.setY(qMax(qMin(1., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.x() + 0.015));
400 int maxWidth = width() - (2 * m_offset);
401 m_zoomStart = m_zoomHandle.x() * maxWidth;
402 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
403 update();
404 return;
405 }
406 // moving zoom zone
407 if (m_hoverZoom) {
408 double clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset) - m_clickOffset;
409 double newX = m_zoomHandle.x() + clickOffset;
410 if (newX < 0) {
411 clickOffset = - m_zoomHandle.x();
412 newX = 0;
413 }
414 double newY = m_zoomHandle.y() + clickOffset;
415 if (newY > 1) {
416 clickOffset = 1 - m_zoomHandle.y();
417 newY = 1;
418 newX = m_zoomHandle.x() + clickOffset;
419 }
420 m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
421 m_zoomHandle = QPointF(newX, newY);
422 int maxWidth = width() - (2 * m_offset);
423 m_zoomStart = m_zoomHandle.x() * maxWidth;
424 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
425 update();
426 }
427 return;
428 }
429 //qDebug()<<"=== MOVING MOUSE: "<<pos<<" = "<<m_currentKeyframe<<", MOVE KFMODE: "<<m_moveKeyframeMode;
430 if ((m_currentKeyframe.second == pos + m_inFrame && m_moveKeyframeMode == TopMove) || (m_currentKeyframe.first == pos + m_inFrame && m_moveKeyframeMode == BottomMove)) {
431 return;
432 }
433 if (m_currentKeyframe.first >= 0 && m_moveKeyframeMode != NoMove) {
434 if (m_moveKeyframeMode == BottomMove) {
435 // Moving bottom keyframe
436 if (!m_keyframes.contains(pos + m_inFrame)) {
437 int delta = pos + m_inFrame - m_currentKeyframe.first;
438 // Check that the move is possible
439 QMapIterator<int, int> i(m_selectedKeyframes);
440 while (i.hasNext()) {
441 i.next();
442 int updatedPos = i.key() + delta;
443 if (!m_selectedKeyframes.contains(updatedPos) && m_keyframes.contains(updatedPos)) {
444 // Don't allow moving over another keyframe
445 qDebug()<<"== MOVE ABORTED; OVERLAPPING EXISTING";
446 return;
447 }
448 }
449 i.toFront();
450 QMap <int,int> updated;
451 while (i.hasNext()) {
452 i.next();
453 if (i.key() < m_currentKeyframe.first) {
454 continue;
455 }
456 //qDebug()<<"=== MOVING KFR: "<<i.key()<<" > "<<(i.key() + delta);
457 //m_keyframes.insert(i.key() + delta, i.value());
458 updated.insert(i.key() + delta, i.value());
459 m_keyframes.remove(i.key());
460 }
461 QMapIterator<int, int> j(updated);
462 while (j.hasNext()) {
463 j.next();
464 m_keyframes.insert(j.key(), j.value());
465 }
466 QMap<int,int> updatedSelection;
467 QMapIterator<int, int> k(m_previousSelection);
468 while (k.hasNext()) {
469 k.next();
470 updatedSelection.insert(k.key() + delta, k.value());
471 }
472 m_previousSelection = updatedSelection;
473 m_currentKeyframe.first += delta;
474 std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
475 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
476 emit selectedKf(m_currentKeyframe, speeds, atEnd);
477
478 m_selectedKeyframes = updated;
479 emit seekToPos(-1, pos);
480 emit updateKeyframes(false);
481 if (remapMax() > m_lastMaxDuration) {
482 m_lastMaxDuration = remapMax();
483 int maxWidth = width() - (2 * m_offset);
484 m_scale = maxWidth / double(qMax(1, m_lastMaxDuration));
485 m_zoomStart = m_zoomHandle.x() * maxWidth;
486 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
487 }
488 update();
489 return;
490 } else {
491 qDebug()<<"=== KEYFRAME :"<< pos<<" ALREADY EXISTS";
492 }
493 } else if (m_moveKeyframeMode == TopMove) {
494 // Moving top keyframe
495 int realPos = qMax(m_inFrame, pos + m_inFrame);
496 //pos = GenTime(m_remapLink->anim_get_double("map", pos)).frames(pCore->getCurrentFps());
497 int delta = realPos - m_currentKeyframe.second;
498 // Check that the move is possible
499 QMapIterator<int, int> i(m_selectedKeyframes);
500 while (i.hasNext()) {
501 i.next();
502 if (i.value() + delta >= m_sourceDuration) {
503 delta = qMin(delta, m_sourceDuration - i.value() - 1);
504 realPos = m_currentKeyframe.second + delta;
505 pos = realPos - m_inFrame;
506 if (delta == 0) {
507 pCore->displayMessage(i18n("Cannot move last source keyframe past clip end"), MessageType::ErrorMessage, 500);
508 return;
509 }
510 }
511 if (i.value() + delta < 0) {
512 delta = qMax(delta, -i.value());
513 realPos = m_currentKeyframe.second + delta;
514 pos = realPos - m_inFrame;
515 if (delta == 0) {
516 pCore->displayMessage(i18n("Cannot move first source keyframe before clip start"), MessageType::ErrorMessage, 500);
517 return;
518 }
519 }
520 }
521 i.toFront();
522 QMap<int,int>updated;
523 while (i.hasNext()) {
524 i.next();
525 m_keyframes.insert(i.key(), i.value() + delta);
526 updated.insert(i.key(), i.value() + delta);
527 if (i.value() == m_currentKeyframe.second) {
528 m_currentKeyframe.second = realPos;
529 }
530 }
531 QMap<int,int> updatedSelection;
532 QMapIterator<int, int> k(m_previousSelection);
533 while (k.hasNext()) {
534 k.next();
535 updatedSelection.insert(k.key(), k.value() + delta);
536 }
537 m_previousSelection = updatedSelection;
538 std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
539 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
540 emit selectedKf(m_currentKeyframe, speeds, atEnd);
541 m_selectedKeyframes = updated;
542 slotSetPosition(pos + m_inFrame);
543 emit seekToPos(pos + m_inFrame, -1);
544 emit updateKeyframes(false);
545 if (remapMax() > m_lastMaxDuration) {
546 m_lastMaxDuration = remapMax();
547 int maxWidth = width() - (2 * m_offset);
548 m_scale = maxWidth / double(qMax(1, m_lastMaxDuration));
549 m_zoomStart = m_zoomHandle.x() * maxWidth;
550 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
551 }
552 update();
553 return;
554 }
555 }
556 // Rubberband selection
557 if (m_clickPoint >= 0) {
558 m_clickEnd = pos + m_inFrame;
559 int min = qMin(m_clickPoint, m_clickEnd);
560 int max = qMax(m_clickPoint, m_clickEnd);
561 min = qMax(1, min);
562 m_selectedKeyframes.clear();
563 m_currentKeyframeOriginal = m_currentKeyframe = {-1,-1};
564 QMapIterator<int, int> i(m_keyframes);
565 while (i.hasNext()) {
566 i.next();
567 if (i.key() > min && i.key() <= max) {
568 m_selectedKeyframes.insert(i.key(), i.value());
569 }
570 }
571 if (!m_selectedKeyframes.isEmpty()) {
572 m_currentKeyframe = {m_selectedKeyframes.firstKey(), m_selectedKeyframes.value(m_selectedKeyframes.firstKey())};
573 m_currentKeyframeOriginal = m_currentKeyframe;
574 }
575 update();
576 return;
577 }
578
579 if (m_moveKeyframeMode == CursorMove) {
580 if (pos != m_position) {
581 slotSetPosition(pos + m_inFrame);
582 emit seekToPos(pos + m_inFrame, -1);
583 }
584 }
585 if (m_moveKeyframeMode == CursorMoveBottom) {
586 pos = qMin(pos, m_keyframes.lastKey() - m_inFrame);
587 if (pos != m_bottomPosition) {
588 m_bottomPosition = pos;
589 bool isKfr = m_keyframes.contains(m_bottomPosition + m_inFrame);
590 if (isKfr) {
591 bool isLast = m_bottomPosition + m_inFrame == m_keyframes.firstKey() || m_bottomPosition + m_inFrame == m_keyframes.lastKey();
592 emit atKeyframe(isKfr, isLast);
593 } else {
594 emit atKeyframe(false, false);
595 }
596 pos = GenTime(m_remapLink->anim_get_double("map", pos + m_inFrame)).frames(pCore->getCurrentFps());
597 slotSetPosition(pos);
598 emit seekToPos(-1, m_bottomPosition);
599 }
600 }
601 return;
602 }
603 if (event->y() > m_bottomView) {
604 // Moving in zoom area
605 if (qAbs(event->x() - m_offset - (m_zoomHandle.x() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
606 setCursor(Qt::SizeHorCursor);
607 m_hoverZoomIn = true;
608 m_hoverZoomOut = false;
609 m_hoverZoom = false;
610 update();
611 return;
612 }
613 if (qAbs(event->x() - m_offset - (m_zoomHandle.y() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
614 setCursor(Qt::SizeHorCursor);
615 m_hoverZoomOut = true;
616 m_hoverZoomIn = false;
617 m_hoverZoom = false;
618 update();
619 return;
620 }
621 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))) {
622 setCursor(Qt::PointingHandCursor);
623 m_hoverZoom = true;
624 m_hoverZoomIn = false;
625 m_hoverZoomOut = false;
626 update();
627 return;
628 }
629 }
630
631 if (m_hoverZoomOut || m_hoverZoomIn || m_hoverZoom) {
632 m_hoverZoomOut = false;
633 m_hoverZoomIn = false;
634 m_hoverZoom = false;
635 setCursor(Qt::ArrowCursor);
636 update();
637 }
638 }
639
position() const640 int RemapView::position() const
641 {
642 return m_position;
643 }
644
centerCurrentKeyframe()645 void RemapView::centerCurrentKeyframe()
646 {
647 if (m_currentKeyframe.first == -1) {
648 // No keyframe selected, abort
649 return;
650 }
651 QMap<int,int>nextKeyframes;
652 if (m_moveNext) {
653 QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first);
654 if (*it != m_keyframes.last() && it != m_keyframes.end()) {
655 it++;
656 while (it != m_keyframes.end()) {
657 nextKeyframes.insert(it.key(), it.value());
658 it++;
659 }
660 }
661 }
662 m_keyframes.remove(m_currentKeyframe.first);
663 int offset = m_bottomPosition + m_inFrame - m_currentKeyframe.first;
664 m_currentKeyframe.first = m_bottomPosition + m_inFrame;
665 if (offset == 0) {
666 // no move
667 return;
668 }
669 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
670 QMapIterator<int, int> i(nextKeyframes);
671 while (i.hasNext()) {
672 i.next();
673 m_keyframes.remove(i.key());
674 }
675 i.toFront();
676 while (i.hasNext()) {
677 i.next();
678 m_keyframes.insert(i.key() + offset, i.value());
679 }
680 std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
681 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
682 emit selectedKf(m_currentKeyframe, speeds, atEnd);
683 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey();
684 emit atKeyframe(true, isLast);
685 emit updateKeyframes(true);
686 update();
687 }
688
centerCurrentTopKeyframe()689 void RemapView::centerCurrentTopKeyframe()
690 {
691 if (m_currentKeyframe.first == -1) {
692 // No keyframe selected, abort
693 return;
694 }
695 //std::pair<int,int> range = getRange(m_currentKeyframe);
696 QMap<int,int>nextKeyframes;
697 int offset = m_position + m_inFrame - m_currentKeyframe.second;
698 if (m_moveNext) {
699 QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first);
700 if (*it != m_keyframes.last() && it != m_keyframes.end()) {
701 it++;
702 while (it != m_keyframes.end()) {
703 nextKeyframes.insert(it.key(), it.value());
704 // Check that the move is possible
705 if (it.value() + offset >= m_sourceDuration) {
706 pCore->displayMessage(i18n("Cannot move last source keyframe past clip end"), MessageType::ErrorMessage, 500);
707 return;
708 }
709 if (it.value() + offset < 0) {
710 pCore->displayMessage(i18n("Cannot move first source keyframe before clip start"), MessageType::ErrorMessage, 500);
711 return;
712 }
713 it++;
714 }
715 }
716 }
717 m_currentKeyframe.second = m_position + m_inFrame;
718 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
719 QMapIterator<int, int> i(nextKeyframes);
720 while (i.hasNext()) {
721 i.next();
722 m_keyframes.insert(i.key(), i.value() + offset);
723 }
724 std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
725 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
726 emit selectedKf(m_currentKeyframe, speeds, atEnd);
727 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey();
728 emit atKeyframe(true, isLast);
729 emit updateKeyframes(true);
730 update();
731 }
732
getClosestKeyframe(int pos,bool bottomKeyframe) const733 std::pair<int,int> RemapView::getClosestKeyframe(int pos, bool bottomKeyframe) const
734 {
735 int deltaMin = -1;
736 std::pair<int,int> result = {-1,-1};
737 QMapIterator<int, int> i(m_keyframes);
738 while (i.hasNext()) {
739 i.next();
740 int val = bottomKeyframe ? i.key() : i.value();
741 int delta = qAbs(val - pos);
742 if (deltaMin == -1 || delta < deltaMin) {
743 deltaMin = delta;
744 result = {i.key(), i.value()};
745 }
746 }
747 return result;
748 }
749
mouseReleaseEvent(QMouseEvent * event)750 void RemapView::mouseReleaseEvent(QMouseEvent *event)
751 {
752 event->accept();
753 bool keyframesEdited = m_keyframesOrigin != m_keyframes;
754 if (m_moveKeyframeMode == TopMove || m_moveKeyframeMode == BottomMove) {
755 // Restore original selection
756 m_selectedKeyframes = m_previousSelection;
757 int maxWidth = width() - (2 * m_offset);
758 m_scale = maxWidth / double(qMax(1, remapMax()));
759 m_zoomStart = m_zoomHandle.x() * maxWidth;
760 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
761 update();
762 if (!keyframesEdited) {
763 emit seekToPos(m_currentKeyframeOriginal.second, m_bottomPosition);
764 }
765 }
766 m_moveKeyframeMode = NoMove;
767 if (keyframesEdited) {
768 emit updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin);
769 }
770 }
771
mousePressEvent(QMouseEvent * event)772 void RemapView::mousePressEvent(QMouseEvent *event)
773 {
774 event->accept();
775 m_lastMaxDuration = remapMax();
776 int pos = int(((event->x() - m_offset) / m_zoomFactor + m_zoomStart ) / m_scale);
777 pos = qBound(0, pos, m_maxLength);
778 m_moveKeyframeMode = NoMove;
779 m_keyframesOrigin = m_keyframes;
780 m_oldInFrame = m_inFrame;
781 if (event->button() == Qt::LeftButton) {
782 if (event->y() < m_centerPos) {
783 // mouse click in top area
784 if (event->modifiers() & Qt::ShiftModifier) {
785 m_clickPoint = pos + m_inFrame;
786 return;
787 }
788 if (event->y() < 2 *m_lineHeight && event->y() > m_lineHeight) {
789 std::pair<int,int> keyframe = getClosestKeyframe(pos + m_inFrame);
790 if (keyframe.first > -1 && qAbs(keyframe.second - (pos + m_inFrame)) * m_scale * m_zoomFactor <= m_lineHeight / 2) {
791 // Clicked on a top keyframe
792 m_currentKeyframeOriginal = keyframe;
793 if (event->modifiers() & Qt::ControlModifier) {
794 if (m_selectedKeyframes.contains(m_currentKeyframeOriginal.first)) {
795 m_selectedKeyframes.remove(m_currentKeyframeOriginal.first);
796 m_currentKeyframeOriginal.first = -1;
797 } else {
798 m_selectedKeyframes.insert(m_currentKeyframeOriginal.first, m_currentKeyframeOriginal.second);
799 }
800 } else if (!m_selectedKeyframes.contains(m_currentKeyframeOriginal.first)) {
801 m_selectedKeyframes = {m_currentKeyframeOriginal};
802 }
803 // Select and seek to keyframe
804 m_currentKeyframe = m_currentKeyframeOriginal;
805 // Calculate speeds
806 std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
807 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
808 emit selectedKf(m_currentKeyframe, speeds, atEnd);
809 if (m_currentKeyframeOriginal.first > -1) {
810 m_moveKeyframeMode = TopMove;
811 m_previousSelection = m_selectedKeyframes;
812 if (m_moveNext) {
813 QMapIterator<int, int> i(m_keyframes);
814 while (i.hasNext()) {
815 i.next();
816 if (i.key() > m_currentKeyframeOriginal.first) {
817 m_selectedKeyframes.insert(i.key(), i.value());
818 }
819 }
820 }
821 if (KdenliveSettings::keyframeseek()) {
822 slotSetPosition(m_currentKeyframeOriginal.second);
823 m_bottomPosition = m_currentKeyframeOriginal.first - m_inFrame;
824 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey();
825 emit atKeyframe(true, isLast);
826 } else {
827 update();
828 }
829 } else {
830 update();
831 }
832 return;
833 }
834 }
835 // no keyframe next to mouse
836 //m_selectedKeyframes.clear();
837 //m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
838 m_moveKeyframeMode = CursorMove;
839 if (pos != m_position) {
840 slotSetPosition(pos + m_inFrame);
841 emit seekToPos(pos + m_inFrame, -1);
842 update();
843 }
844 return;
845 } else if (event->y() > m_bottomView) {
846 // click on zoom area
847 if (m_hoverZoom) {
848 m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
849 }
850 return;
851 } else if (event->y() > m_centerPos && event->y() < m_bottomView) {
852 // click in bottom area
853 if (event->modifiers() & Qt::ShiftModifier) {
854 m_clickPoint = pos + m_inFrame;
855 return;
856 }
857 if (event->y() > (m_bottomView - 2 * m_lineHeight) && (event->y() < m_bottomView - m_lineHeight)) {
858 std::pair<int,int> keyframe = getClosestKeyframe(pos + m_inFrame, true);
859 if (keyframe.first > -1 && qAbs(keyframe.first - (pos + m_inFrame)) * m_scale * m_zoomFactor <= m_lineHeight / 2) {
860 m_currentKeyframeOriginal = keyframe;
861 if (event->modifiers() & Qt::ControlModifier) {
862 if (m_selectedKeyframes.contains(m_currentKeyframeOriginal.first)) {
863 m_selectedKeyframes.remove(m_currentKeyframeOriginal.first);
864 m_currentKeyframeOriginal.second = -1;
865 } else {
866 m_selectedKeyframes.insert(m_currentKeyframeOriginal.first, m_currentKeyframeOriginal.second);
867 }
868 } else if (!m_selectedKeyframes.contains(m_currentKeyframeOriginal.first)) {
869 m_selectedKeyframes = {m_currentKeyframeOriginal};
870 }
871 // Select and seek to keyframe
872 m_currentKeyframe = m_currentKeyframeOriginal;
873 std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
874 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
875 emit selectedKf(m_currentKeyframe, speeds, atEnd);
876 if (m_currentKeyframeOriginal.first > -1) {
877 m_moveKeyframeMode = BottomMove;
878 m_previousSelection = m_selectedKeyframes;
879 if (m_moveNext) {
880 QMapIterator<int, int> i(m_keyframes);
881 while (i.hasNext()) {
882 i.next();
883 if (i.key() > m_currentKeyframeOriginal.first) {
884 m_selectedKeyframes.insert(i.key(), i.value());
885 }
886 }
887 }
888 if (KdenliveSettings::keyframeseek()) {
889 m_bottomPosition = m_currentKeyframeOriginal.first - m_inFrame;
890 int topPos = GenTime(m_remapLink->anim_get_double("map", m_currentKeyframeOriginal.first)).frames(pCore->getCurrentFps());
891 m_position = topPos - m_inFrame;
892 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey();
893 emit atKeyframe(true, isLast);
894 } else {
895 update();
896 }
897 } else {
898 update();
899 }
900 return;
901 }
902 }
903 // no keyframe next to mouse
904 //m_selectedKeyframes.clear();
905 //m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
906 m_moveKeyframeMode = CursorMoveBottom;
907 pos = qMin(pos, m_keyframes.lastKey() - m_inFrame);
908 if (pos != m_bottomPosition) {
909 m_bottomPosition = pos;
910 bool isKfr = m_keyframes.contains(m_bottomPosition + m_inFrame);
911 if (isKfr) {
912 bool isLast = m_bottomPosition + m_inFrame == m_keyframes.firstKey() || m_bottomPosition + m_inFrame == m_keyframes.lastKey();
913 emit atKeyframe(isKfr, isLast);
914 } else {
915 emit atKeyframe(false, false);
916 }
917 //slotUpdatePosition();
918 emit seekToPos(-1, pos);
919 update();
920 }
921 /*int topPos = GenTime(m_remapLink->anim_get_double("map", pos + m_inFrame)).frames(pCore->getCurrentFps());
922 if (topPos != m_position + m_inFrame) {
923
924 slotSetPosition(topPos);
925 emit seekToPos(-1, pos);
926 update();
927 }*/
928 return;
929 }
930 } else if (event->button() == Qt::RightButton && event->y() > m_bottomView) {
931 // Right click on zoom, switch between no zoom and last zoom status
932 if (m_zoomHandle == QPointF(0, 1)) {
933 if (!m_lastZoomHandle.isNull()) {
934 m_zoomHandle = m_lastZoomHandle;
935 int maxWidth = width() - (2 * m_offset);
936 m_zoomStart = m_zoomHandle.x() * maxWidth;
937 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
938 update();
939 return;
940 }
941 } else {
942 m_lastZoomHandle = m_zoomHandle;
943 m_zoomHandle = QPointF(0, 1);
944 int maxWidth = width() - (2 * m_offset);
945 m_zoomStart = m_zoomHandle.x() * maxWidth;
946 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
947 update();
948 return;
949 }
950 }
951 if (pos != m_position) {
952 //emit seekToPos(pos);
953 update();
954 }
955 }
956
wheelEvent(QWheelEvent * event)957 void RemapView::wheelEvent(QWheelEvent *event)
958 {
959 if (event->modifiers() & Qt::AltModifier) {
960 // Alt modifier seems to invert x/y axis
961 if (event->angleDelta().x() > 0) {
962 goPrev();
963 } else {
964 goNext();
965 }
966 return;
967 }
968 if (event->modifiers() & Qt::ControlModifier) {
969 int maxWidth = width() - 2 * m_offset;
970 double scaledPos = m_position * m_scale;
971 double zoomRange = (m_zoomHandle.y() - m_zoomHandle.x()) * maxWidth;
972 if (event->angleDelta().y() > 0) {
973 zoomRange /= 1.5;
974 } else {
975 zoomRange *= 1.5;
976 }
977 if (zoomRange < 5) {
978 // Don't allow too small zoombar
979 return;
980 }
981 double length = (scaledPos - zoomRange / 2) / maxWidth;
982 m_zoomHandle.setX(qMax(0., length));
983 if (length < 0) {
984 m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth - length));
985 } else {
986 m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth));
987 }
988 m_zoomStart = m_zoomHandle.x() * maxWidth;
989 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
990 update();
991 return;
992 }
993 int change = event->angleDelta().y() > 0 ? -1 : 1;
994 int pos = qBound(0, m_position + change, m_duration - 1);
995 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
996 if (event->position().y() < m_bottomView) {
997 #else
998 if (event->y() < m_bottomView) {
999 #endif
1000 emit seekToPos(pos + m_inFrame, -1);
1001 } else {
1002 // Wheel on zoom bar, scroll
1003 double pos = m_zoomHandle.x();
1004 double zoomWidth = m_zoomHandle.y() - pos;
1005 int maxWidth = width() - 2 * m_offset;
1006 if (event->angleDelta().y() > 0) {
1007 if (zoomWidth / 2 > pos) {
1008 pos = 0;
1009 } else {
1010 pos -= zoomWidth / 2;
1011 }
1012 } else {
1013 if (pos + zoomWidth + zoomWidth / 2 > 1.) {
1014 pos = 1. - zoomWidth;
1015 } else {
1016 pos += zoomWidth / 2;
1017 }
1018 }
1019 m_zoomHandle = QPointF(pos, pos + zoomWidth);
1020 m_zoomStart = m_zoomHandle.x() * maxWidth;
1021 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
1022 update();
1023 }
1024 }
1025
1026
1027 void RemapView::slotSetPosition(int pos)
1028 {
1029 if (pos != m_position + m_inFrame) {
1030 m_position = pos - m_inFrame;
1031 //int offset = pCore->getItemIn(m_model->getOwnerId());
1032 //emit atKeyframe(m_keyframes.contains(pos));
1033 double zoomPos = double(m_position) / m_duration;
1034 if (zoomPos < m_zoomHandle.x()) {
1035 double interval = m_zoomHandle.y() - m_zoomHandle.x();
1036 zoomPos = qBound(0.0, zoomPos - interval / 5, 1.0);
1037 m_zoomHandle.setX(zoomPos);
1038 m_zoomHandle.setY(zoomPos + interval);
1039 } else if (zoomPos > m_zoomHandle.y()) {
1040 double interval = m_zoomHandle.y() - m_zoomHandle.x();
1041 zoomPos = qBound(0.0, zoomPos + interval / 5, 1.0);
1042 m_zoomHandle.setX(zoomPos - interval);
1043 m_zoomHandle.setY(zoomPos);
1044 }
1045 update();
1046 }
1047 }
1048
1049 void RemapView::slotSetBottomPosition(int pos)
1050 {
1051 if (pos < 0 || pos + m_inFrame > m_keyframes.lastKey()) {
1052 pos = -1;
1053 }
1054 if (pos != m_bottomPosition) {
1055 m_bottomPosition = pos;
1056 if (m_bottomPosition > -1) {
1057 bool isKfr = m_keyframes.contains(m_bottomPosition + m_inFrame);
1058 if (isKfr) {
1059 bool isLast = m_bottomPosition + m_inFrame == m_keyframes.firstKey() || m_bottomPosition + m_inFrame == m_keyframes.lastKey();
1060 emit atKeyframe(isKfr, isLast);
1061 } else {
1062 emit atKeyframe(false, false);
1063 }
1064 } else {
1065 emit atKeyframe(false, false);
1066 }
1067 update();
1068 }
1069 }
1070
1071 void RemapView::goNext()
1072 {
1073 // Seek to next keyframe
1074 QMapIterator<int, int> i(m_keyframes);
1075 while (i.hasNext()) {
1076 i.next();
1077 if (i.key() > m_bottomPosition + m_inFrame) {
1078 m_currentKeyframe = {i.key(),i.value()};
1079 m_selectedKeyframes = {m_currentKeyframe};
1080 slotSetPosition(i.key());
1081 m_bottomPosition = m_currentKeyframe.first - m_inFrame;
1082 emit seekToPos(i.value(), m_bottomPosition);
1083 std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
1084 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
1085 emit selectedKf(m_currentKeyframe, speeds, atEnd);
1086 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey();
1087 emit atKeyframe(true, isLast);
1088 break;
1089 }
1090 }
1091 }
1092
1093 void RemapView::goPrev()
1094 {
1095 // insert keyframe at interpolated position
1096 bool previousFound = false;
1097 QMap<int, int>::const_iterator it = m_keyframes.constBegin();
1098 while (it.key() < m_bottomPosition + m_inFrame && it != m_keyframes.constEnd()) {
1099 it++;
1100 }
1101 if (it != m_keyframes.constEnd()) {
1102 if (it != m_keyframes.constBegin()) {
1103 it--;
1104 }
1105 m_currentKeyframe = {it.key(), it.value()};
1106 m_selectedKeyframes = {m_currentKeyframe};
1107 slotSetPosition(m_currentKeyframe.second);
1108 m_bottomPosition = m_currentKeyframe.first - m_inFrame;
1109 emit seekToPos(m_currentKeyframe.second, m_bottomPosition);
1110 std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
1111 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
1112 emit selectedKf(m_currentKeyframe, speeds, atEnd);
1113 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey();
1114 emit atKeyframe(true, isLast);
1115 previousFound = true;
1116 }
1117
1118 if (!previousFound && !m_keyframes.isEmpty()) {
1119 // We are after the last keyframe
1120 m_currentKeyframe = {m_keyframes.lastKey(), m_keyframes.value(m_keyframes.lastKey())};
1121 m_selectedKeyframes = {m_currentKeyframe};
1122 slotSetPosition(m_currentKeyframe.second);
1123 m_bottomPosition = m_currentKeyframe.first - m_inFrame;
1124 emit seekToPos(m_currentKeyframe.second, m_bottomPosition);
1125 std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
1126 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
1127 emit selectedKf(m_currentKeyframe, speeds, atEnd);
1128 }
1129 }
1130
1131 void RemapView::updateBeforeSpeed(double speed)
1132 {
1133 QMutexLocker lock(&m_kfrMutex);
1134 QMap<int, int>::const_iterator it = m_keyframes.constFind(m_currentKeyframe.first);
1135 QMap<int, int> updatedKfrs;
1136 QList<int> toDelete;
1137 if (*it != m_keyframes.first() && it != m_keyframes.constEnd()) {
1138 m_keyframesOrigin = m_keyframes;
1139 it--;
1140 int updatedLength = qFuzzyIsNull(speed) ? 0 : (m_currentKeyframe.second - it.value()) * 100. / speed;
1141 int offset = it.key() + updatedLength - m_currentKeyframe.first;
1142 m_keyframes.remove(m_currentKeyframe.first);
1143 m_currentKeyframe.first = it.key() + updatedLength;
1144 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
1145 m_bottomPosition = m_currentKeyframe.first;
1146 m_selectedKeyframes.clear();
1147 m_selectedKeyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
1148 it = m_keyframes.constFind(m_currentKeyframe.first);
1149 if (*it != m_keyframes.last()) {
1150 it++;
1151 // Update all keyframes after that so that we don't alter the speeds
1152 while (m_moveNext && it != m_keyframes.constEnd()) {
1153 toDelete << it.key();
1154 updatedKfrs.insert(it.key() + offset, it.value());
1155 it++;
1156 }
1157 }
1158 for (int p : qAsConst(toDelete)) {
1159 m_keyframes.remove(p);
1160 }
1161 QMapIterator<int, int> i(updatedKfrs);
1162 while (i.hasNext()) {
1163 i.next();
1164 m_keyframes.insert(i.key(), i.value());
1165 }
1166 int maxWidth = width() - (2 * m_offset);
1167 m_scale = maxWidth / double(qMax(1, remapMax()));
1168 m_zoomStart = m_zoomHandle.x() * maxWidth;
1169 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
1170 emit updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin);
1171 update();
1172 }
1173 }
1174
1175 void RemapView::updateAfterSpeed(double speed)
1176 {
1177 QMutexLocker lock(&m_kfrMutex);
1178 QMap<int, int>::const_iterator it = m_keyframes.constFind(m_currentKeyframe.first);
1179 if (*it != m_keyframes.last()) {
1180 m_keyframesOrigin = m_keyframes;
1181 it++;
1182 QMap<int, int> updatedKfrs;
1183 QList<int> toDelete;
1184 int updatedLength = qFuzzyIsNull(speed) ? 0 : (it.value() - m_currentKeyframe.second) * 100. / speed;
1185 int offset = m_currentKeyframe.first + updatedLength - it.key();
1186 if (m_moveNext) {
1187 while (it != m_keyframes.constEnd()) {
1188 toDelete << it.key();
1189 updatedKfrs.insert(it.key() + offset, it.value());
1190 it++;
1191 }
1192 } else {
1193 m_keyframes.insert(m_currentKeyframe.first + updatedLength, it.value());
1194 m_keyframes.remove(it.key());
1195 }
1196 // Update all keyframes after that so that we don't alter the speeds
1197 for (int p : qAsConst(toDelete)) {
1198 m_keyframes.remove(p);
1199 }
1200 QMapIterator<int, int> i(updatedKfrs);
1201 while (i.hasNext()) {
1202 i.next();
1203 m_keyframes.insert(i.key(), i.value());
1204 }
1205 int maxWidth = width() - (2 * m_offset);
1206 m_scale = maxWidth / double(qMax(1, remapMax()));
1207 m_zoomStart = m_zoomHandle.x() * maxWidth;
1208 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
1209 emit updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin);
1210 update();
1211 }
1212 }
1213
1214 const QString RemapView::getKeyframesData(QMap<int,int> keyframes) const
1215 {
1216 QStringList result;
1217 if (keyframes.isEmpty()) {
1218 keyframes = m_keyframes;
1219 }
1220 QMapIterator<int, int> i(keyframes);
1221 int offset = 0;
1222 while (i.hasNext()) {
1223 i.next();
1224 if (i.key() == keyframes.lastKey()) {
1225 // HACK: we always set last keyframe 1 frame after in MLT to ensure we have a correct last frame
1226 offset = 1;
1227 }
1228 Mlt::Properties props;
1229 props.set("_profile", pCore->getProjectProfile()->get_profile(), 0);
1230 result << QString("%1=%2").arg(props.frames_to_time(i.key() + offset, mlt_time_clock)).arg(GenTime(i.value(), pCore->getCurrentFps()).seconds());
1231 }
1232 return result.join(QLatin1Char(';'));
1233 }
1234
1235 void RemapView::reloadProducer()
1236 {
1237 if (!m_clip || !m_clip->clipUrl().endsWith(QLatin1String(".mlt"))) {
1238 qDebug()<<"==== this is not a playlist clip, aborting";
1239 return;
1240 }
1241 Mlt::Consumer c(pCore->getCurrentProfile()->profile(), "xml", m_clip->clipUrl().toUtf8().constData());
1242 QScopedPointer<Mlt::Service> serv(m_clip->originalProducer()->producer());
1243 if (serv == nullptr) {
1244 return;
1245 }
1246 qDebug()<<"==== GOR PLAYLIST SERVICE: "<<serv->type()<<" / "<<serv->consumer()->type()<<", SAVING TO "<<m_clip->clipUrl();
1247 Mlt::Multitrack s2(*serv.data());
1248 qDebug()<<"==== MULTITRACK: "<<s2.count();
1249 Mlt::Tractor s(pCore->getCurrentProfile()->profile());
1250 s.set_track(*s2.track(0), 0);
1251 qDebug()<<"==== GOT TRACKS: "<<s.count();
1252 int ignore = s.get_int("ignore_points");
1253 if (ignore) {
1254 s.set("ignore_points", 0);
1255 }
1256 c.connect(s);
1257 c.set("time_format", "frames");
1258 c.set("no_meta", 1);
1259 c.set("no_root", 1);
1260 //c.set("no_profile", 1);
1261 c.set("root", "/");
1262 c.set("store", "kdenlive");
1263 c.run();
1264 if (ignore) {
1265 s.set("ignore_points", ignore);
1266 }
1267 }
1268
1269 std::pair<double,double> RemapView::getSpeed(std::pair<int,int>kf)
1270 {
1271 std::pair<double,double> speeds = {-1,-1};
1272 QMap<int, int>::const_iterator it = m_keyframes.constFind(kf.first);
1273 if (it == m_keyframes.constEnd()) {
1274 // Not a keyframe
1275 return speeds;
1276 }
1277 if (*it != m_keyframes.first()) {
1278 it--;
1279 speeds.first = (double)(kf.second - it.value()) / (kf.first - it.key());
1280 it++;
1281 }
1282 if (*it != m_keyframes.last()) {
1283 it++;
1284 speeds.second = (double)(kf.second - it.value()) / (kf.first - it.key());
1285 }
1286 return speeds;
1287 }
1288
1289 void RemapView::addKeyframe()
1290 {
1291 // insert or remove keyframe at interpolated position
1292 m_keyframesOrigin = m_keyframes;
1293 if (m_keyframes.contains(m_bottomPosition + m_inFrame)) {
1294 m_keyframes.remove(m_bottomPosition + m_inFrame);
1295 if (m_currentKeyframe.first == m_bottomPosition + m_inFrame) {
1296 m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
1297 emit selectedKf(m_currentKeyframe, {-1,-1});
1298 }
1299 emit atKeyframe(false, false);
1300 emit updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin);
1301 update();
1302 return;
1303 }
1304 QMapIterator<int, int> i(m_keyframes);
1305 std::pair<int, int> newKeyframe = {-1,-1};
1306 std::pair<int, int> previous = {-1,-1};
1307 newKeyframe.first = m_bottomPosition + m_inFrame;
1308 while (i.hasNext()) {
1309 i.next();
1310 if (i.key() > m_bottomPosition + m_inFrame) {
1311 if (i.key() == m_keyframes.firstKey()) {
1312 // This is the first keyframe
1313 double ratio = (double)(m_bottomPosition + m_inFrame) / i.key();
1314 newKeyframe.second = i.value() * ratio;
1315 break;
1316 } else if (previous.first > -1) {
1317 std::pair<int,int> current = {i.key(), i.value()};
1318 double ratio = (double)(m_bottomPosition + m_inFrame - previous.first) / (current.first - previous.first);
1319 qDebug()<<"=== RATIO: "<<ratio;
1320 newKeyframe.second = previous.second + (qAbs(current.second - previous.second) * ratio);
1321 break;
1322 }
1323 }
1324 previous = {i.key(), i.value()};
1325 }
1326 if (newKeyframe.second == -1) {
1327 // We are after the last keyframe
1328 if (m_keyframes.isEmpty()) {
1329 newKeyframe.second = m_position + m_inFrame;
1330 } else {
1331 double ratio = (double)(m_position + m_inFrame - m_keyframes.lastKey()) / (m_duration - m_keyframes.lastKey());
1332 newKeyframe.second = m_keyframes.value(m_keyframes.lastKey()) + (qAbs(m_duration - m_keyframes.value(m_keyframes.lastKey())) * ratio);
1333 }
1334 }
1335 m_keyframes.insert(newKeyframe.first, newKeyframe.second);
1336 m_currentKeyframe = newKeyframe;
1337 m_selectedKeyframes = {m_currentKeyframe};
1338 std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
1339 std::pair<bool,bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()};
1340 emit selectedKf(m_currentKeyframe, speeds, atEnd);
1341 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey();
1342 emit atKeyframe(true, isLast);
1343 emit updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin);
1344 update();
1345 }
1346
1347 void RemapView::toggleMoveNext(bool moveNext)
1348 {
1349 m_moveNext = moveNext;
1350 // Reset keyframe selection
1351 m_selectedKeyframes.clear();
1352 }
1353
1354 void RemapView::refreshOnDurationChanged(int remapDuration)
1355 {
1356 if (remapDuration != m_duration) {
1357 m_duration = qMax(remapDuration, remapMax());
1358 int maxWidth = width() - (2 * m_offset);
1359 m_scale = maxWidth / double(qMax(1, remapMax()));
1360 m_zoomStart = m_zoomHandle.x() * maxWidth;
1361 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
1362 }
1363 }
1364
1365 void RemapView::resizeEvent(QResizeEvent *event)
1366 {
1367 int maxWidth = width() - (2 * m_offset);
1368 m_scale = maxWidth / double(qMax(1, remapMax()));
1369 m_zoomStart = m_zoomHandle.x() * maxWidth;
1370 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
1371 QWidget::resizeEvent(event);
1372 update();
1373 }
1374
1375 void RemapView::paintEvent(QPaintEvent *event)
1376 {
1377 Q_UNUSED(event)
1378 QPalette pal = palette();
1379 KColorScheme scheme(pal.currentColorGroup(), KColorScheme::Window);
1380 m_colSelected = palette().highlight().color();
1381 m_colKeyframe = scheme.foreground(KColorScheme::NormalText).color();
1382 QColor bg = scheme.background(KColorScheme::AlternateBackground ).color();
1383 QStylePainter p(this);
1384 int maxWidth = width() - (2 * m_offset);
1385 int zoomEnd = qCeil(m_zoomHandle.y() * maxWidth);
1386 // Top timeline
1387 p.fillRect(m_offset, 0, maxWidth + 1, m_centerPos, bg);
1388 // Bottom timeline
1389 p.fillRect(m_offset, m_bottomView - m_centerPos, maxWidth + 1, m_centerPos, bg);
1390 /* ticks */
1391 double fps = pCore->getCurrentFps();
1392 int maxLength = remapMax();
1393 if (maxLength == 0) {
1394 return;
1395 }
1396 int displayedLength = int(maxLength / m_zoomFactor / fps);
1397 double factor = 1;
1398 if (displayedLength < 2) {
1399 // 1 frame tick
1400 } else if (displayedLength < 30 ) {
1401 // 1 sec tick
1402 factor = fps;
1403 } else if (displayedLength < 150) {
1404 // 5 sec tick
1405 factor = 5 * fps;
1406 } else if (displayedLength < 300) {
1407 // 10 sec tick
1408 factor = 10 * fps;
1409 } else if (displayedLength < 900) {
1410 // 30 sec tick
1411 factor = 30 * fps;
1412 } else if (displayedLength < 1800) {
1413 // 1 min. tick
1414 factor = 60 * fps;
1415 } else if (displayedLength < 9000) {
1416 // 5 min tick
1417 factor = 300 * fps;
1418 } else if (displayedLength < 18000) {
1419 // 10 min tick
1420 factor = 600 * fps;
1421 } else {
1422 // 30 min tick
1423 factor = 1800 * fps;
1424 }
1425
1426 // Position of left border in frames
1427 double tickOffset = m_zoomStart * m_zoomFactor;
1428 double frameSize = factor * m_scale * m_zoomFactor;
1429 int base = int(tickOffset / frameSize);
1430 tickOffset = frameSize - (tickOffset - (base * frameSize));
1431 // Draw frame ticks
1432 int scaledTick = 0;
1433 for (int i = 0; i < maxWidth / frameSize; i++) {
1434 scaledTick = int(m_offset + (i * frameSize) + tickOffset);
1435 if (scaledTick >= maxWidth + m_offset) {
1436 break;
1437 }
1438 p.drawLine(QPointF(scaledTick , m_lineHeight + 1), QPointF(scaledTick, m_lineHeight - 3));
1439 p.drawLine(QPointF(scaledTick , m_bottomView - m_lineHeight + 1), QPointF(scaledTick, m_bottomView - m_lineHeight - 3));
1440 }
1441
1442 /*
1443 * Time-"lines"
1444 * We have a top timeline for the source (clip monitor) and a bottom timeline for the output (project monitor)
1445 */
1446 p.setPen(m_colKeyframe);
1447 // Top timeline
1448 //qDebug()<<"=== MAX KFR WIDTH: "<<maxWidth<<", DURATION SCALED: "<<(m_duration * m_scale)<<", POS: "<<(m_position * m_scale);
1449 int maxPos = maxWidth + m_offset - 1;
1450 p.drawLine(m_offset, m_lineHeight, maxPos, m_lineHeight);
1451 p.drawLine(m_offset, m_lineHeight - m_lineHeight / 4, m_offset, m_lineHeight + m_lineHeight / 4);
1452 p.drawLine(maxPos, m_lineHeight - m_lineHeight / 4, maxPos, m_lineHeight + m_lineHeight / 4);
1453 // Bottom timeline
1454 p.drawLine(m_offset, m_bottomView - m_lineHeight, maxPos, m_bottomView - m_lineHeight);
1455 p.drawLine(m_offset, m_bottomView - m_lineHeight - m_lineHeight / 4, m_offset, m_bottomView - m_lineHeight + m_lineHeight / 4);
1456 p.drawLine(maxPos, m_bottomView - m_lineHeight - m_lineHeight / 4, maxPos, m_bottomView - m_lineHeight + m_lineHeight / 4);
1457
1458 /*
1459 * Original clip in/out
1460 */
1461 p.setPen(palette().mid().color());
1462 double inPos = (double)(m_originalRange.first - m_inFrame) * m_scale;
1463 double outPos = (double)(m_originalRange.second - m_inFrame) * m_scale;
1464 inPos -= m_zoomStart;
1465 inPos *= m_zoomFactor;
1466 outPos -= m_zoomStart;
1467 outPos *= m_zoomFactor;
1468 if (inPos >= 0) {
1469 inPos += m_offset;
1470 p.drawLine(inPos, m_lineHeight, inPos, m_bottomView - m_lineHeight);
1471 }
1472 if (outPos <= maxWidth) {
1473 outPos += m_offset;
1474 p.drawLine(outPos, m_lineHeight, outPos, m_bottomView - m_lineHeight);
1475 }
1476
1477 /*
1478 * Keyframes
1479 */
1480 QMapIterator<int, int> i(m_keyframes);
1481 while (i.hasNext()) {
1482 i.next();
1483 double outPos = (double)(i.key() - m_inFrame) * m_scale;
1484 double inPos = (double)(i.value() - m_inFrame) * m_scale;
1485 if ((inPos < m_zoomStart && outPos < m_zoomStart) || (qFloor(inPos) > zoomEnd && qFloor(outPos) > zoomEnd)) {
1486 continue;
1487 }
1488 if (m_currentKeyframe.first == i.key()) {
1489 p.setPen(Qt::red);
1490 p.setBrush(Qt::darkRed);
1491 } else if (m_selectedKeyframes.contains(i.key())) {
1492 p.setPen(m_colSelected);
1493 p.setBrush(m_colSelected);
1494 } else {
1495 p.setPen(m_colKeyframe);
1496 p.setBrush(m_colKeyframe);
1497 }
1498 inPos -= m_zoomStart;
1499 inPos *= m_zoomFactor;
1500 inPos += m_offset;
1501 outPos -= m_zoomStart;
1502 outPos *= m_zoomFactor;
1503 outPos += m_offset;
1504
1505 p.drawLine(inPos, m_lineHeight + m_lineHeight * 0.75, outPos, m_bottomView - m_lineHeight * 1.75);
1506 p.drawLine(inPos, m_lineHeight, inPos, m_lineHeight + m_lineHeight / 2);
1507 p.drawLine(outPos, m_bottomView - m_lineHeight, outPos, m_bottomView - m_lineHeight * 1.5);
1508 p.drawEllipse(QRectF(inPos - m_lineHeight / 4.0, m_lineHeight + m_lineHeight / 2, m_lineHeight / 2, m_lineHeight / 2));
1509 p.drawEllipse(QRectF(outPos - m_lineHeight / 4.0, m_bottomView - 2 * m_lineHeight, m_lineHeight / 2, m_lineHeight / 2));
1510 }
1511
1512 /*
1513 * current position cursor
1514 */
1515 p.setPen(m_colSelected);
1516 // Top seek cursor
1517 if (m_position >= 0 && m_position < m_duration) {
1518 p.setBrush(m_colSelected);
1519 double scaledPos = m_position * m_scale;
1520 scaledPos -= m_zoomStart;
1521 scaledPos *= m_zoomFactor;
1522 scaledPos += m_offset;
1523 if (scaledPos >= m_offset && qFloor(scaledPos) <= m_offset + maxWidth) {
1524 QPolygonF topCursor;
1525 topCursor << QPointF(-int(m_lineHeight / 3), -m_lineHeight * 0.5) << QPointF(int(m_lineHeight / 3), -m_lineHeight * 0.5) << QPointF(0, 0);
1526 topCursor.translate(scaledPos, m_lineHeight);
1527 p.drawPolygon(topCursor);
1528 }
1529 }
1530
1531 if (m_bottomPosition >= 0 && m_bottomPosition < m_duration) {
1532 p.setBrush(m_colSelected);
1533 int topPos = -1;
1534 double scaledPos = -1;
1535 if (m_remapLink && !m_keyframes.isEmpty()) {
1536 topPos = GenTime(m_remapLink->anim_get_double("map", m_bottomPosition + m_inFrame)).frames(pCore->getCurrentFps()) - m_inFrame;
1537 scaledPos = topPos * m_scale;
1538 scaledPos -= m_zoomStart;
1539 scaledPos *= m_zoomFactor;
1540 scaledPos += m_offset;
1541 }
1542 double scaledPos2 = m_bottomPosition * m_scale;
1543 scaledPos2 -= m_zoomStart;
1544 scaledPos2 *= m_zoomFactor;
1545 scaledPos2 += m_offset;
1546 if (scaledPos2 >= m_offset && qFloor(scaledPos2) <= m_offset + maxWidth) {
1547 QPolygonF bottomCursor;
1548 bottomCursor << QPointF(-int(m_lineHeight / 3), m_lineHeight * 0.5) << QPointF(int(m_lineHeight / 3), m_lineHeight * 0.5) << QPointF(0, 0);
1549 bottomCursor.translate(scaledPos2, m_bottomView - m_lineHeight);
1550 p.setBrush(m_colSelected);
1551 p.drawPolygon(bottomCursor );
1552 }
1553 if (scaledPos > -1) {
1554 p.drawLine(scaledPos, m_lineHeight * 1.75, scaledPos2, m_bottomView - (m_lineHeight * 1.75));
1555 p.drawLine(scaledPos, m_lineHeight, scaledPos, m_lineHeight * 1.75);
1556 }
1557 p.drawLine(scaledPos2, m_bottomView - m_lineHeight, scaledPos2, m_bottomView - m_lineHeight * 1.75);
1558 }
1559
1560 // Zoom bar
1561 p.setPen(Qt::NoPen);
1562 p.setBrush(palette().mid());
1563 p.drawRoundedRect(0, m_bottomView + 2, width() - 2 * 0, m_zoomHeight, m_lineHeight / 3, m_lineHeight / 3);
1564 p.setBrush(palette().highlight());
1565 p.drawRoundedRect(int((width()) * m_zoomHandle.x()),
1566 m_bottomView + 2,
1567 int((width()) * (m_zoomHandle.y() - m_zoomHandle.x())),
1568 m_zoomHeight,
1569 m_lineHeight / 3, m_lineHeight / 3);
1570 }
1571
1572
1573 TimeRemap::TimeRemap(QWidget *parent)
1574 : QWidget(parent)
1575 , m_cid(-1)
1576 {
1577 setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
1578 setupUi(this);
1579
1580 m_in = new TimecodeDisplay(pCore->timecode(), this);
1581 inLayout->addWidget(m_in);
1582 m_out = new TimecodeDisplay(pCore->timecode(), this);
1583 outLayout->addWidget(m_out);
1584 m_view = new RemapView(this);
1585 speedBefore->setKeyboardTracking(false);
1586 speedAfter->setKeyboardTracking(false);
1587 remapLayout->addWidget(m_view);
1588 connect(m_view, &RemapView::selectedKf, this, [this](std::pair<int,int>selection, std::pair<double,double>speeds, std::pair<bool,bool>atEnd) {
1589 info_frame->setEnabled(selection.first > -1);
1590 QSignalBlocker bk(m_in);
1591 QSignalBlocker bk2(m_out);
1592 m_in->setValue(selection.second - m_view->m_inFrame);
1593 m_out->setValue(selection.first - m_view->m_inFrame);
1594 QSignalBlocker bk3(speedBefore);
1595 QSignalBlocker bk4(speedAfter);
1596 speedBefore->setEnabled(!atEnd.first);
1597 speedBefore->setValue(100. * speeds.first);
1598 speedAfter->setEnabled(!atEnd.second);
1599 speedAfter->setValue(100. * speeds.second);
1600 });
1601 connect(m_view, &RemapView::updateSpeeds, this, [this](std::pair<double,double>speeds) {
1602 QSignalBlocker bk3(speedBefore);
1603 QSignalBlocker bk4(speedAfter);
1604 speedBefore->setEnabled(speeds.first > 0);
1605 speedBefore->setValue(100. * speeds.first);
1606 speedAfter->setEnabled(speeds.second > 0);
1607 speedAfter->setValue(100. * speeds.second);
1608 });
1609 button_add->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-add")));
1610 button_add->setToolTip(i18n("Add keyframe"));
1611 button_next->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-next")));
1612 button_next->setToolTip(i18n("Go to next keyframe"));
1613 button_prev->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-previous")));
1614 button_prev->setToolTip(i18n("Go to previous keyframe"));
1615 connect(m_view, &RemapView::updateKeyframes, this, &TimeRemap::updateKeyframes);
1616 connect(m_view, &RemapView::updateKeyframesWithUndo, this, &TimeRemap::updateKeyframesWithUndo);
1617 connect(m_in, &TimecodeDisplay::timeCodeUpdated, this, [this]() {
1618 m_view->updateInPos(m_in->getValue() + m_view->m_inFrame);
1619 });
1620 button_center->setToolTip(i18n("Move selected keyframe to cursor"));
1621 button_center_top->setToolTip(i18n("Move selected keyframe to cursor"));
1622 connect(m_out, &TimecodeDisplay::timeCodeUpdated, this, [this]() {
1623 m_view->updateOutPos(m_out->getValue() + m_view->m_inFrame);
1624 });
1625 connect(button_center, &QToolButton::clicked, m_view, &RemapView::centerCurrentKeyframe);
1626 connect(button_center_top, &QToolButton::clicked, m_view, &RemapView::centerCurrentTopKeyframe);
1627 connect(m_view, &RemapView::atKeyframe, button_add, [&](bool atKeyframe, bool last) {
1628 if (atKeyframe) {
1629 button_add->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-remove")));
1630 button_add->setToolTip(i18n("Delete keyframe"));
1631 } else {
1632 button_add->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-add")));
1633 button_add->setToolTip(i18n("Add keyframe"));
1634 }
1635 button_add->setEnabled(!atKeyframe || !last);
1636 });
1637 connect(speedBefore, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [&](double speed) {
1638 m_view->updateBeforeSpeed(speed);
1639 });
1640 connect(speedAfter, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [&](double speed) {
1641 m_view->updateAfterSpeed(speed);
1642 });
1643 connect(button_del, &QToolButton::clicked, this, [this]() {
1644 if (m_cid > -1) {
1645 std::shared_ptr<TimelineItemModel> model = pCore->window()->getCurrentTimeline()->controller()->getModel();
1646 model->requestClipTimeRemap(m_cid, false);
1647 selectedClip(-1);
1648 }
1649 });
1650 connect(button_add, &QToolButton::clicked, m_view, &RemapView::addKeyframe);
1651 connect(button_next, &QToolButton::clicked, m_view, &RemapView::goNext);
1652 connect(button_prev, &QToolButton::clicked, m_view, &RemapView::goPrev);
1653 connect(move_next, &QCheckBox::toggled, m_view, &RemapView::toggleMoveNext);
1654 connect(pitch_compensate, &QCheckBox::toggled, this, &TimeRemap::switchRemapParam);
1655 connect(frame_blending, &QCheckBox::toggled, this, &TimeRemap::switchRemapParam);
1656 connect(m_view, &RemapView::updateMaxDuration, this, [this]() {
1657 m_out->setRange(0, INT_MAX);
1658 //m_in->setRange(0, duration - 1);
1659 });
1660 setEnabled(false);
1661 }
1662
1663 const QString &TimeRemap::currentClip() const
1664 {
1665 return m_binId;
1666 }
1667
1668 void TimeRemap::checkClipUpdate(const QModelIndex &topLeft, const QModelIndex &, const QVector<int>& roles)
1669 {
1670 int id = int(topLeft.internalId());
1671 if (m_cid != id || !roles.contains(TimelineModel::FinalMoveRole)) {
1672 return;
1673 }
1674 // Don't resize view if we are moving a keyframe
1675 if (!m_view->movingKeyframe()) {
1676 int newDuration = pCore->getItemDuration({ObjectType::TimelineClip,m_cid});
1677 // Check if the keyframes were modified by an external resize operation
1678 std::shared_ptr<TimelineItemModel> model = pCore->window()->getCurrentTimeline()->controller()->getModel();
1679 std::shared_ptr<ClipModel> clip = model->getClipPtr(m_cid);
1680 QMap<QString,QString> values = clip->getRemapValues();
1681 if (values.value(QLatin1String("map")) == m_view->getKeyframesData()) {
1682 // Resize was triggered by our keyframe move, nothing to do
1683 return;
1684 }
1685 // Reload keyframes
1686 m_lastLength = newDuration;
1687 m_view->m_remapLink->set("map", values.value(QLatin1String("map")).toUtf8().constData());
1688 int min = pCore->getItemIn({ObjectType::TimelineClip,m_cid});
1689 m_view->m_startPos = pCore->getItemPosition({ObjectType::TimelineClip,m_cid});
1690 m_in->setRange(0, m_view->m_maxLength - min);
1691 m_out->setRange(0, INT_MAX);
1692 m_view->loadKeyframes(values.value(QLatin1String("map")));
1693 m_view->update();
1694 }
1695 }
1696
1697 void TimeRemap::selectedClip(int cid)
1698 {
1699 if (cid == -1 && cid == m_cid) {
1700 return;
1701 }
1702 QObject::disconnect( m_seekConnection1 );
1703 QObject::disconnect( m_seekConnection2 );
1704 QObject::disconnect( m_seekConnection3 );
1705 connect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekRemap, m_view, &RemapView::slotSetPosition, Qt::UniqueConnection);
1706 m_cid = cid;
1707 std::shared_ptr<TimelineItemModel> model = pCore->window()->getCurrentTimeline()->controller()->getModel();
1708 disconnect(model.get(), &TimelineItemModel::dataChanged, this, &TimeRemap::checkClipUpdate);
1709 if (cid == -1) {
1710 m_binId.clear();
1711 m_view->setDuration(nullptr, -1);
1712 setEnabled(false);
1713 return;
1714 }
1715 m_view->m_remapLink.reset();
1716 connect(model.get(), &TimelineItemModel::dataChanged, this, &TimeRemap::checkClipUpdate);
1717 model->requestClipTimeRemap(cid);
1718 m_splitId = model->m_groups->getSplitPartner(cid);
1719 m_binId = model->getClipBinId(cid);
1720 m_lastLength = pCore->getItemDuration({ObjectType::TimelineClip,cid});
1721 m_view->m_startPos = pCore->getItemPosition({ObjectType::TimelineClip,cid});
1722 std::shared_ptr<Mlt::Producer> prod = model->getClipProducer(cid);
1723 m_view->m_maxLength = prod->get_length();
1724 m_in->setRange(0, m_view->m_maxLength - prod->get_in());
1725 //m_in->setRange(0, m_lastLength - 1);
1726 m_out->setRange(0, INT_MAX);
1727 m_view->setDuration(prod, m_lastLength, prod->parent().get_length());
1728 qDebug()<<"===== GOT PRODUCER TYPE: "<<prod->parent().type();
1729 if (prod->parent().type() == mlt_service_chain_type) {
1730 Mlt::Chain fromChain(prod->parent());
1731 int count = fromChain.link_count();
1732 for (int i = 0; i < count; i++) {
1733 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i));
1734 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) {
1735 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) {
1736 // Found a timeremap effect, read params
1737 m_view->m_remapLink = std::make_shared<Mlt::Link>(fromChain.link(i)->get_link());
1738 if (m_splitId > -1) {
1739 std::shared_ptr<Mlt::Producer> prod2 = model->getClipProducer(m_splitId);
1740 if (prod2->parent().type() == mlt_service_chain_type) {
1741 Mlt::Chain fromChain2(prod2->parent());
1742 count = fromChain2.link_count();
1743 for (int j = 0; j < count; j++) {
1744 QScopedPointer<Mlt::Link> fromLink2(fromChain2.link(j));
1745 if (fromLink2 && fromLink2->is_valid() && fromLink2->get("mlt_service")) {
1746 if (fromLink2->get("mlt_service") == QLatin1String("timeremap")) {
1747 m_splitRemap = std::make_shared<Mlt::Link>(fromChain2.link(j)->get_link());
1748 }
1749 }
1750 }
1751 }
1752 }
1753 QString mapData(fromLink->get("map"));
1754 m_view->loadKeyframes(mapData);
1755 if (mapData.isEmpty()) {
1756 // We are just adding the remap effect, set default params
1757 fromLink->set("pitch", 1);
1758 fromLink->set("image_mode", "nearest");
1759 }
1760 QSignalBlocker bk(pitch_compensate);
1761 QSignalBlocker bk2(frame_blending);
1762 pitch_compensate->setChecked(fromLink->get_int("pitch") == 1);
1763 frame_blending->setChecked(fromLink->get("image_mode") != QLatin1String("nearest"));
1764 setEnabled(true);
1765 break;
1766 }
1767 }
1768 }
1769 } else {
1770 qDebug()<<"/// PRODUCER IS NOT A CHAIN!!!!";
1771 }
1772 if (!m_binId.isEmpty() && pCore->getMonitor(Kdenlive::ClipMonitor)->activeClipId() == m_binId) {
1773 connect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekPosition, pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekRemap, Qt::UniqueConnection);
1774 }
1775 m_seekConnection1 = connect(m_view, &RemapView::seekToPos, this, [this](int topPos, int bottomPos) {
1776 if (topPos > -1) {
1777 if (pCore->getMonitor(Kdenlive::ClipMonitor)->activeClipId() != m_binId) {
1778 int min = pCore->getItemIn({ObjectType::TimelineClip,m_cid});
1779 int lastLength = pCore->getItemDuration({ObjectType::TimelineClip,m_cid});
1780 int max = min + lastLength;
1781 pCore->selectBinClip(m_binId, true, min, {min,max});
1782 }
1783 pCore->getMonitor(Kdenlive::ClipMonitor)->requestSeek(topPos);
1784 }
1785 if (bottomPos > -1) {
1786 pCore->getMonitor(Kdenlive::ProjectMonitor)->requestSeek(bottomPos + m_view->m_startPos);
1787 }
1788 });
1789 m_seekConnection2 = connect(pCore->getMonitor(Kdenlive::ProjectMonitor), &Monitor::seekPosition, this, [this](int pos) {
1790 m_view->slotSetBottomPosition(pos - m_view->m_startPos);
1791 });
1792 }
1793
1794 void TimeRemap::setClip(std::shared_ptr<ProjectClip> clip, int in, int out)
1795 {
1796 if (m_cid > -1 && clip == nullptr) {
1797 return;
1798 }
1799 QObject::disconnect( m_seekConnection1 );
1800 QObject::disconnect( m_seekConnection2 );
1801 QObject::disconnect( m_seekConnection3 );
1802 m_cid = -1;
1803 m_binId.clear();
1804 if (clip == nullptr || !clip->statusReady() || clip->clipType() != ClipType::Playlist) {
1805 m_view->setDuration(nullptr, -1);
1806 setEnabled(false);
1807 return;
1808 }
1809 m_view->m_remapLink.reset();
1810 bool keyframesLoaded = false;
1811 if (clip != nullptr) {
1812 int min = in == -1 ? 0 : in;
1813 int max = out == -1 ? clip->getFramePlaytime() : out;
1814 m_in->setRange(0, max - min);
1815 m_out->setRange(min, INT_MAX);
1816 m_view->m_startPos = 0;
1817 m_view->setBinClipDuration(clip, max - min);
1818 if (clip->clipType() == ClipType::Playlist) {
1819 Mlt::Service service(clip->originalProducer()->producer()->get_service());
1820 qDebug()<<"==== producer type: "<<service.type();
1821 if (service.type() == mlt_service_multitrack_type) {
1822 Mlt::Multitrack multi(service);
1823 for (int i = 0; i < multi.count(); i++) {
1824 std::unique_ptr<Mlt::Producer> track(multi.track(i));
1825 qDebug()<<"==== GOT TRACK TYPE: "<<track->type();
1826 switch (track->type()) {
1827 case mlt_service_chain_type: {
1828 Mlt::Chain fromChain(*track.get());
1829 int count = fromChain.link_count();
1830 for (int i = 0; i < count; i++) {
1831 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i));
1832 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) {
1833 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) {
1834 // Found a timeremap effect, read params
1835 m_view->m_remapLink = std::make_shared<Mlt::Link>(fromChain.link(i)->get_link());
1836 QString mapData(fromLink->get("map"));
1837 m_view->loadKeyframes(mapData);
1838 keyframesLoaded = true;
1839 break;
1840 }
1841 }
1842 }
1843 break;
1844 }
1845 case mlt_service_playlist_type: {
1846 // that is a single track
1847 Mlt::Playlist local_playlist(*track);
1848 int max = local_playlist.count();
1849 qDebug()<<"==== PLAYLIST COUNT: "<<max;
1850 if (max == 1) {
1851 Mlt::Producer prod = local_playlist.get_clip(0)->parent();
1852 qDebug()<<"==== GOT PROD TYPE: "<<prod.type()<<" = "<<prod.get("mlt_service")<<" = "<<prod.get("resource");
1853 if (prod.type() == mlt_service_chain_type) {
1854 Mlt::Chain fromChain(prod);
1855 int count = fromChain.link_count();
1856 for (int i = 0; i < count; i++) {
1857 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i));
1858 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) {
1859 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) {
1860 // Found a timeremap effect, read params
1861 m_view->m_remapLink = std::make_shared<Mlt::Link>(fromChain.link(i)->get_link());
1862 QString mapData(fromLink->get("map"));
1863 m_view->loadKeyframes(mapData);
1864 keyframesLoaded = true;
1865 break;
1866 }
1867 }
1868 }
1869 }
1870 }
1871 break;
1872 }
1873 default:
1874 qDebug()<<"=== UNHANDLED TRACK TYPE";
1875 break;
1876 }
1877 }
1878 }
1879 }
1880 if (!keyframesLoaded) {
1881 m_view->loadKeyframes(QString());
1882 }
1883 m_seekConnection1 = connect(m_view, &RemapView::seekToPos, pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::requestSeek, Qt::UniqueConnection);
1884 m_seekConnection2 = connect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekPosition, this, [&](int pos) {
1885 m_view->slotSetPosition(pos);
1886 });
1887 setEnabled(m_view->m_remapLink != nullptr);
1888 } else {
1889 setEnabled(false);
1890 }
1891 }
1892
1893 void TimeRemap::updateKeyframes(bool resize)
1894 {
1895 QString kfData = m_view->getKeyframesData();
1896 if (m_view->m_remapLink) {
1897 m_view->m_remapLink->set("map", kfData.toUtf8().constData());
1898 if (m_splitRemap) {
1899 m_splitRemap->set("map", kfData.toUtf8().constData());
1900 }
1901 if (m_cid == -1) {
1902 // This is a playlist clip
1903 m_view->timer.start();
1904 }
1905 }
1906 }
1907
1908 void TimeRemap::updateKeyframesWithUndo(QMap<int,int>updatedKeyframes, QMap<int,int>previousKeyframes)
1909 {
1910 if (m_view->m_remapLink == nullptr) {
1911 return;
1912 }
1913 bool usePitch = pitch_compensate->isChecked();
1914 bool useBlend = frame_blending->isChecked();
1915 bool hadPitch = m_view->m_remapLink->get_int("pitch") == 1;
1916 bool hadBlend = m_view->m_remapLink->get("image_mode") != QLatin1String("nearest");
1917 bool durationChanged = updatedKeyframes.isEmpty() ? false : updatedKeyframes.lastKey() - pCore->getItemIn({ObjectType::TimelineClip,m_cid}) + 1 != pCore->getItemDuration({ObjectType::TimelineClip,m_cid});
1918 Fun undo = []() { return true; };
1919 Fun redo = []() { return true; };
1920 Fun local_undo = [this, link = m_view->m_remapLink, splitLink = m_splitRemap, previousKeyframes, cid = m_cid, oldIn = m_view->m_oldInFrame, hadPitch, hadBlend]() {
1921 QString oldKfData;
1922 bool keyframesChanged = false;
1923 if (!previousKeyframes.isEmpty()) {
1924 oldKfData = m_view->getKeyframesData(previousKeyframes);
1925 keyframesChanged = true;
1926 }
1927 if (keyframesChanged) {
1928 link->set("map", oldKfData.toUtf8().constData());
1929 }
1930 link->set("pitch", hadPitch ? 1 : 0);
1931 link->set("image_mode", hadBlend ? "blend" : "nearest");
1932 if (splitLink) {
1933 if (keyframesChanged) {
1934 splitLink->set("map", oldKfData.toUtf8().constData());
1935 }
1936 splitLink->set("pitch", hadPitch ? 1 : 0);
1937 splitLink->set("image_mode", hadBlend ? "blend" : "nearest");
1938 }
1939 if (cid == m_cid) {
1940 QSignalBlocker bk(pitch_compensate);
1941 QSignalBlocker bk2(frame_blending);
1942 pitch_compensate->setChecked(hadPitch);
1943 frame_blending->setChecked(hadBlend);
1944 if (keyframesChanged) {
1945 m_lastLength = previousKeyframes.lastKey() - oldIn;
1946 // This clip is currently displayed in remap view
1947 m_view->m_remapLink->set("map", oldKfData.toUtf8().constData());
1948 m_view->loadKeyframes(oldKfData);
1949 update();
1950 }
1951 }
1952 return true;
1953 };
1954
1955 Fun local_redo = [this, link = m_view->m_remapLink, splitLink = m_splitRemap, updatedKeyframes, cid = m_cid, usePitch, in = m_view->m_inFrame, useBlend]() {
1956 QString newKfData;
1957 bool keyframesChanged = false;
1958 if (!updatedKeyframes.isEmpty()) {
1959 newKfData = m_view->getKeyframesData(updatedKeyframes);
1960 keyframesChanged = true;
1961 }
1962 if (keyframesChanged) {
1963 link->set("map", newKfData.toUtf8().constData());
1964 }
1965 link->set("pitch", usePitch ? 1 : 0);
1966 link->set("image_mode", useBlend ? "blend" : "nearest");
1967 if (splitLink) {
1968 if (keyframesChanged) {
1969 splitLink->set("map", newKfData.toUtf8().constData());
1970 }
1971 splitLink->set("pitch", usePitch ? 1 : 0);
1972 splitLink->set("image_mode", useBlend ? "blend" : "nearest");
1973 }
1974 if (cid == m_cid) {
1975 QSignalBlocker bk(pitch_compensate);
1976 QSignalBlocker bk2(frame_blending);
1977 pitch_compensate->setChecked(usePitch);
1978 frame_blending->setChecked(useBlend);
1979 if (keyframesChanged) {
1980 // This clip is currently displayed in remap view
1981 m_lastLength = updatedKeyframes.lastKey() - in;
1982 m_view->m_remapLink->set("map", newKfData.toUtf8().constData());
1983 m_view->loadKeyframes(newKfData);
1984 update();
1985 }
1986 }
1987 return true;
1988 };
1989 local_redo();
1990 if (durationChanged) {
1991 int length = updatedKeyframes.lastKey() - m_view->m_inFrame + 1;
1992 std::shared_ptr<TimelineItemModel> model = pCore->window()->getCurrentTimeline()->controller()->getModel();
1993 model->requestItemResize(m_cid, length, true, true, undo, redo);
1994 if (m_splitId > 0) {
1995 model->requestItemResize(m_splitId, length, true, true, undo, redo);
1996 }
1997 }
1998 UPDATE_UNDO_REDO_NOLOCK(redo, undo, local_undo, local_redo);
1999 pCore->pushUndo(local_undo, local_redo, i18n("Edit Timeremap keyframes"));
2000 }
2001
2002 void TimeRemap::switchRemapParam()
2003 {
2004 updateKeyframesWithUndo(QMap<int,int>(),QMap<int,int>());
2005 }
2006
2007 bool TimeRemap::isInRange() const
2008 {
2009 return m_cid != -1 && m_view->isInRange();
2010 }
2011
2012 TimeRemap::~TimeRemap()
2013 {
2014 //delete m_previewTimer;
2015 }
2016