timeremap.cpp 56.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/***************************************************************************
 *   Copyright (C) 2021 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
 ***************************************************************************/

#include "timeremap.h"

#include "core.h"
#include "doc/kthumb.h"
#include "kdenlivesettings.h"
#include "bin/projectclip.h"
#include "project/projectmanager.h"
27
#include "monitor/monitor.h"
28
29
30
31
#include "profiles/profilemodel.hpp"
#include "mainwindow.h"
#include "timeline2/view/timelinewidget.h"
#include "timeline2/view/timelinecontroller.h"
32
#include "timeline2/model/groupsmodel.hpp"
33
#include "macros.hpp"
34
35
36
37
38

#include "kdenlive_debug.h"
#include <QFontDatabase>
#include <QWheelEvent>
#include <QStylePainter>
39
#include <QtMath>
40
41
42
43
44
45
46

#include <KColorScheme>
#include "klocalizedstring.h"

RemapView::RemapView(QWidget *parent)
    : QWidget(parent)
    , m_duration(1)
47
48
    , m_position(0)
    , m_scale(1.)
49
50
    , m_zoomFactor(1)
    , m_zoomStart(0)
51
52
    , m_zoomHandle(0,1)
    , m_moveKeyframeMode(NoMove)
53
    , m_clip(nullptr)
54
    , m_service(nullptr)
55
56
    , m_clickPoint(-1)
    , m_moveNext(true)
57
58
{
    setMouseTracking(true);
59
    setMinimumSize(QSize(150, 80));
60
61
    setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
    int size = QFontInfo(font()).pixelSize() * 3;
62
    setFixedHeight(size * 4);
63
64
65
    m_lineHeight = int(size / 2.);
    m_zoomHeight = m_lineHeight * 0.5;
    m_offset = qCeil(m_lineHeight / 4);
66
    m_bottomView = height() - m_zoomHeight - m_lineHeight - 5;
67
    setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
68
69
70
71
    int maxWidth = width() - (2 * m_offset);
    m_scale = maxWidth / double(qMax(1, m_duration - 1));
    m_zoomStart = m_zoomHandle.x() * maxWidth;
    m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
72
73
74
    timer.setInterval(500);
    timer.setSingleShot(true);
    connect(&timer, &QTimer::timeout, this, &RemapView::reloadProducer);
75
76
77
78
79
80
81
82
83
84
85
86
}

void RemapView::updateInPos(int pos)
{
    if (m_currentKeyframe.first > -1) {
        if (m_keyframes.contains(pos)) {
            // Cannot move kfr over an existing one
            return;
        }
        m_keyframes.insert(pos, m_currentKeyframe.second);
        m_keyframes.remove(m_currentKeyframe.first);
        m_currentKeyframe.first = pos;
87
        updateKeyframes();
88
89
90
91
92
93
94
95
96
97
98
99
100
        update();
    }
}

void RemapView::updateOutPos(int pos)
{
    if (m_currentKeyframe.second > -1) {
        if (m_keyframes.values().contains(pos)) {
            // Cannot move kfr over an existing one
            return;
        }
        m_keyframes.insert(m_currentKeyframe.first, pos);
        m_currentKeyframe.second = pos;
101
        updateKeyframes();
102
103
        update();
    }
104
105
}

106
107
108
109
110
111
112
113
114
115
116
117
118
119
int RemapView::remapDuration() const
{
    int maxDuration = 0;
    QMapIterator<int, int> i(m_keyframes);
    while (i.hasNext()) {
        i.next();
        if (i.value() > maxDuration) {
            maxDuration = i.value();
        }
    }
    return maxDuration;
}

void RemapView::setBinClipDuration(std::shared_ptr<ProjectClip> clip, int duration)
120
{
121
    m_clip = clip;
122
123
    m_service = clip->originalProducer();
    m_duration = duration;
124
125
    int maxWidth = width() - (2 * m_offset);
    m_scale = maxWidth / double(qMax(1, m_duration - 1));
126
127
128
129
130
131
132
    m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
}

void RemapView::setDuration(std::shared_ptr<Mlt::Producer> service, int duration)
{
    m_clip = nullptr;
    m_service = service;
133
    m_duration = duration;
134
135
    int maxWidth = width() - (2 * m_offset);
    m_scale = maxWidth / double(qMax(1, m_duration - 1));
136
137
138
    m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
}

139
void RemapView::loadKeyframes(const QString &mapData)
140
141
142
143
144
{
    m_keyframes.clear();
    if (mapData.isEmpty()) {
        m_keyframes.insert(0, 0);
        m_keyframes.insert(m_duration - 1, m_duration - 1);
145
        updateKeyframes();
146
147
148
    } else {
        QStringList str = mapData.split(QLatin1Char(';'));
        for (auto &s : str) {
149
            int pos = m_service->time_to_frames(s.section(QLatin1Char('='), 0, 0).toUtf8().constData());
150
151
            int val = GenTime(s.section(QLatin1Char('='), 1).toDouble()).frames(pCore->getCurrentFps());
            m_keyframes.insert(val, pos);
152
153
            m_duration = qMax(m_duration, pos);
            m_duration = qMax(m_duration, val);
154
        }
155
156
157
158
        int maxWidth = width() - (2 * m_offset);
        m_scale = maxWidth / double(qMax(1, m_duration - 1));
        m_zoomStart = m_zoomHandle.x() * maxWidth;
        m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
159
        emit updateMaxDuration(m_duration);
160
161
162
163
164
165
166
167
168
        if (m_keyframes.contains(m_currentKeyframe.first)) {
            emit atKeyframe(true);
            std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
            emit selectedKf(m_currentKeyframe, speeds);
        } else {
            emit atKeyframe(false);
            m_currentKeyframe = {-1,-1};
            emit selectedKf(m_currentKeyframe, {-1,-1});
        }
169
170
171
172
173
174
175
176
177
178
179
    }
    update();
}

void RemapView::mouseMoveEvent(QMouseEvent *event)
{
    event->accept();
    double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
    double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
    double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
    int pos = int(((double(event->x()) - m_offset) / zoomFactor + zoomStart ) / m_scale);
180
    int realPos = qMax(0, pos);
181
182
    pos = qBound(0, pos, m_duration - 1);
    GenTime position(pos, pCore->getCurrentFps());
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
    if (event->buttons() == Qt::NoButton) {
        bool hoverKeyframe = false;
        if (event->y() > m_lineHeight && event->y() < 2 * m_lineHeight) {
            // mouse click in top keyframes area
            int keyframe = getClosestKeyframe(pos);
            if (keyframe > -1 && qAbs(keyframe - pos) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
                hoverKeyframe = true;
            }
        } else if (event->y() > m_bottomView - m_lineHeight && event->y() < m_bottomView) {
            // click in bottom keyframe area
            int keyframe = getClosestKeyframe(pos, true);
            if (keyframe > -1 && qAbs(keyframe - pos) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
                hoverKeyframe = true;
            }
        }
        if (hoverKeyframe) {
            setCursor(Qt::PointingHandCursor);
        } else {
            setCursor(Qt::ArrowCursor);
        }
    } else if ((event->buttons() & Qt::LeftButton) != 0u) {
204
205
206
207
        if (m_hoverZoomIn || m_hoverZoomOut || m_hoverZoom) {
            // Moving zoom handles
            if (m_hoverZoomIn) {
                m_zoomHandle.setX(qMin(qMax(0., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.y() - 0.015));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
208
209
210
211
                int maxWidth = width() - (2 * m_offset);
                m_scale = maxWidth / double(qMax(1, m_duration - 1));
                m_zoomStart = m_zoomHandle.x() * maxWidth;
                m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
212
213
214
215
216
                update();
                return;
            }
            if (m_hoverZoomOut) {
                m_zoomHandle.setY(qMax(qMin(1., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.x() + 0.015));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
217
218
219
220
                int maxWidth = width() - (2 * m_offset);
                m_scale = maxWidth / double(qMax(1, m_duration - 1));
                m_zoomStart = m_zoomHandle.x() * maxWidth;
                m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
                update();
                return;
            }
            // moving zoom zone
            if (m_hoverZoom) {
                double clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset) - m_clickOffset;
                double newX = m_zoomHandle.x() + clickOffset;
                if (newX < 0) {
                    clickOffset = - m_zoomHandle.x();
                    newX = 0;
                }
                double newY = m_zoomHandle.y() + clickOffset;
                if (newY > 1) {
                    clickOffset = 1 - m_zoomHandle.y();
                    newY = 1;
                    newX = m_zoomHandle.x() + clickOffset;
                }
                m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
                m_zoomHandle = QPointF(newX, newY);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
240
241
242
243
                int maxWidth = width() - (2 * m_offset);
                m_scale = maxWidth / double(qMax(1, m_duration - 1));
                m_zoomStart = m_zoomHandle.x() * maxWidth;
                m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
                update();
            }
            return;
        }
        //qDebug()<<"=== MOVING MOUSE: "<<pos<<" = "<<m_currentKeyframe<<", MOVE KFMODE: "<<m_moveKeyframeMode;
        if ((m_currentKeyframe.first == pos && m_moveKeyframeMode == TopMove) || (m_currentKeyframe.second == pos && m_moveKeyframeMode == BottomMove)) {
            return;
        }
        if (m_currentKeyframe.first >= 0 && m_moveKeyframeMode != NoMove) {
            if (m_moveKeyframeMode == TopMove) {
                // Moving top keyframe
                if (!m_keyframes.contains(pos)) {
                    int delta = pos - m_currentKeyframe.first;
                    // Check that the move is possible
                    QMapIterator<int, int> i(m_selectedKeyframes);
                    while (i.hasNext()) {
                        i.next();
                        int updatedPos = i.key() + delta;
                        if (!m_selectedKeyframes.contains(updatedPos) && m_keyframes.contains(updatedPos)) {
                            // Don't allow moving over another keyframe
                            qDebug()<<"== MOVE ABORTED; OVERLAPPING EXISTING";
                            return;
                        }
                    }
                    i.toFront();
                    QMap <int,int> updated;
                    while (i.hasNext()) {
                        i.next();
                        //qDebug()<<"=== MOVING KFR: "<<i.key()<<" > "<<(i.key() + delta);
                        m_keyframes.insert(i.key() + delta, i.value());
                        updated.insert(i.key() + delta, i.value());
                        m_keyframes.remove(i.key());
                        if (i.key() == m_currentKeyframe.first) {
                            m_currentKeyframe.first = pos;
                            std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
                            emit selectedKf(m_currentKeyframe, speeds);
                        }
                    }
                    m_selectedKeyframes = updated;
                    updateKeyframes();
284
285
286
                    slotSetPosition(pos, true);
                    emit seekToPos(pos, -1);
                    return;
287
288
289
                } else {
                    qDebug()<<"=== KEYFRAME :"<< pos<<" ALREADY EXISTS";
                }
290
            } else if (m_moveKeyframeMode == BottomMove) {
291
292
                // Moving bottom keyframe
                auto kfrValues = m_keyframes.values();
293
                //pos = GenTime(m_remapLink->anim_get_double("map", pos)).frames(pCore->getCurrentFps());
294
295
                if (!kfrValues.contains(realPos)) {
                    int delta = realPos - m_currentKeyframe.second;
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
                    // Check that the move is possible
                    auto selectedValues = m_selectedKeyframes.values();
                    QMapIterator<int, int> i(m_selectedKeyframes);
                    while (i.hasNext()) {
                        i.next();
                        int updatedPos = i.value() + delta;
                        if (!selectedValues.contains(updatedPos) && kfrValues.contains(updatedPos)) {
                            // Don't allow moving over another keyframe
                            return;
                        }
                    }
                    i.toFront();
                    QMap <int,int> updated;
                    while (i.hasNext()) {
                        i.next();
                        m_keyframes.insert(i.key(), i.value() + delta);
                        updated.insert(i.key(), i.value() + delta);
                        if (i.value() == m_currentKeyframe.second) {
314
                            m_currentKeyframe.second = realPos;
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
                        }
                    }
                    // Update all keyframes after selection
                    if (m_moveNext && m_selectedKeyframes.count() == 1) {
                        QMapIterator<int, int> j(m_keyframes);
                        while (j.hasNext()) {
                            j.next();
                            if (j.value() != m_currentKeyframe.second && j.value() > m_currentKeyframe.second - delta) {
                                m_keyframes.insert(j.key(), j.value() + delta);
                            }
                        }
                    }
                    std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
                    emit selectedKf(m_currentKeyframe, speeds);
                    m_selectedKeyframes = updated;
                    updateKeyframes();
331
                    emit seekToPos(-1, pos);
332
                    update();
333
                    return;
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
                }
            }
        }
        // Rubberband selection
        if (m_clickPoint >= 0) {
            m_clickEnd = pos;
            int min = qMin(m_clickPoint, m_clickEnd);
            int max = qMax(m_clickPoint, m_clickEnd);
            min = qMax(1, min);
            m_selectedKeyframes.clear();
            m_currentKeyframeOriginal = m_currentKeyframe = {-1,-1};
            QMapIterator<int, int> i(m_keyframes);
            while (i.hasNext()) {
                i.next();
                if (i.key() > min && i.key() <= max) {
                    m_selectedKeyframes.insert(i.key(), i.value());
                }
            }
            if (!m_selectedKeyframes.isEmpty()) {
                m_currentKeyframe = {m_selectedKeyframes.firstKey(), m_selectedKeyframes.value(m_selectedKeyframes.firstKey())};
                m_currentKeyframeOriginal = m_currentKeyframe;
            }
            update();
            return;
        }

360
        if (m_moveKeyframeMode == CursorMove || (event->y() < 2 * m_lineHeight)) {
361
            if (pos != m_position) {
362
                slotSetPosition(pos, true);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
363
                emit seekToPos(pos, -1);
364
365
366
367
368
369
            }
        }
        if (m_moveKeyframeMode == CursorMoveBottom || (event->y() > m_bottomView)) {
            pos = GenTime(m_remapLink->anim_get_double("map", pos)).frames(pCore->getCurrentFps());
            if (pos != getKeyframePosition()) {
                slotSetPosition(pos, true);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
370
                emit seekToPos(-1, getKeyframePosition());
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
            }
        }
        return;
    }
    if (event->y() < m_lineHeight) {
        int closest = getClosestKeyframe(pos);
        if (closest > -1 && qAbs(((pos - closest) * m_scale) * m_zoomFactor) < QApplication::startDragDistance()) {
            m_hoverKeyframe = {closest, false};
            setCursor(Qt::PointingHandCursor);
            m_hoverZoomIn = false;
            m_hoverZoomOut = false;
            m_hoverZoom = false;
            update();
            return;
        }
    } else if (event->y() > m_bottomView + m_lineHeight) {
        // Moving in zoom area
        if (qAbs(event->x() - m_offset - (m_zoomHandle.x() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
            setCursor(Qt::SizeHorCursor);
            m_hoverZoomIn = true;
            m_hoverZoomOut = false;
            m_hoverZoom = false;
            update();
            return;
        }
        if (qAbs(event->x() - m_offset - (m_zoomHandle.y() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
            setCursor(Qt::SizeHorCursor);
            m_hoverZoomOut = true;
            m_hoverZoomIn = false;
            m_hoverZoom = false;
            update();
            return;
        }
        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))) {
            setCursor(Qt::PointingHandCursor);
            m_hoverZoom = true;
            m_hoverZoomIn = false;
            m_hoverZoomOut = false;
            update();
            return;
        }
    } else if (event->y() > m_bottomView) {
        // Bottom keyframes
        int closest = getClosestKeyframe(pos, true);
        if (closest > -1 && qAbs(((pos - closest) * m_scale) * m_zoomFactor) < QApplication::startDragDistance()) {
            m_hoverKeyframe = {closest, true};
            setCursor(Qt::PointingHandCursor);
            m_hoverZoomIn = false;
            m_hoverZoomOut = false;
            m_hoverZoom = false;
            update();
            return;
        }
    }

    if (m_hoverKeyframe.first != -1 || m_hoverZoomOut || m_hoverZoomIn || m_hoverZoom) {
        m_hoverKeyframe.first = -1;
        m_hoverZoomOut = false;
        m_hoverZoomIn = false;
        m_hoverZoom = false;
        setCursor(Qt::ArrowCursor);
        update();
    }
}

436
437
438
439
440
int RemapView::position() const
{
    return m_position;
}

441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
int RemapView::getClosestKeyframe(int pos, bool bottomKeyframe) const
{
    int deltaMin = -1;
    int closest = -1;
    QMapIterator<int, int> i(m_keyframes);
    while (i.hasNext()) {
        i.next();
        int val = bottomKeyframe ? i.value() : i.key();
        int delta = qAbs(val - pos);
        if (deltaMin == -1 || delta < deltaMin) {
            deltaMin = delta;
            closest = val;
        }
    }
    return closest;
}

void RemapView::mouseReleaseEvent(QMouseEvent *event)
{
    event->accept();
    m_moveKeyframeMode = NoMove;
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
    if (m_keyframesOrigin != m_keyframes) {
        Fun undo = [this, kfr = m_keyframesOrigin]() {
            m_keyframes = kfr;
            if (m_keyframes.contains(m_currentKeyframe.first)) {
                emit atKeyframe(true);
                std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
                emit selectedKf(m_currentKeyframe, speeds);
            } else {
                emit atKeyframe(false);
                m_currentKeyframe = {-1,-1};
                emit selectedKf(m_currentKeyframe, {-1,-1});
            }
            update();
            return true;
        };
        Fun redo = [this, kfr2 = m_keyframes]() {
            m_keyframes = kfr2;
            if (m_keyframes.contains(m_currentKeyframe.first)) {
                emit atKeyframe(true);
                std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
                emit selectedKf(m_currentKeyframe, speeds);
            } else {
                emit atKeyframe(false);
                m_currentKeyframe = {-1,-1};
                emit selectedKf(m_currentKeyframe, {-1,-1});
            }
            update();
            return true;
        };
        pCore->pushUndo(undo, redo, i18n("Edit Timeremap keyframes"));
    }
493
494
495
496
497
498
499
500
501
502
503
504
    qDebug()<<"=== MOUSE RELEASE!!!!!!!!!!!!!";
}

void RemapView::mousePressEvent(QMouseEvent *event)
{
    event->accept();
    double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
    double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
    double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
    int pos = int(((event->x() - m_offset) / zoomFactor + zoomStart ) / m_scale);
    pos = qBound(0, pos, m_duration - 1);
    m_moveKeyframeMode = NoMove;
505
    m_keyframesOrigin = m_keyframes;
506
    if (event->button() == Qt::LeftButton) {
507
        if (event->y() > m_lineHeight && event->y() < 2 * m_lineHeight) {
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
            // mouse click in top keyframes area
            if (event->modifiers() & Qt::ShiftModifier) {
                m_clickPoint = pos;
                return;
            }
            int keyframe = getClosestKeyframe(pos);
            qDebug()<<"==== KEYFRAME AREA CLICK! CLOSEST KFR: "<<keyframe;
            if (keyframe > -1 && qAbs(keyframe - pos) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
                m_currentKeyframeOriginal = {keyframe, m_keyframes.value(keyframe)};
                if (event->modifiers() & Qt::ControlModifier) {
                    if (m_selectedKeyframes.contains(m_currentKeyframeOriginal.first)) {
                        m_selectedKeyframes.remove(m_currentKeyframeOriginal.first);
                        m_currentKeyframeOriginal.first = -1;
                    } else {
                        m_selectedKeyframes.insert(m_currentKeyframeOriginal.first, m_currentKeyframeOriginal.second);
                    }
                } else if (!m_selectedKeyframes.contains(m_currentKeyframeOriginal.first)) {
                    m_selectedKeyframes = {m_currentKeyframeOriginal};
                }
                // Select and seek to keyframe
                m_currentKeyframe = m_currentKeyframeOriginal;
                // Calculate speeds
                std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
                emit selectedKf(m_currentKeyframe, speeds);
                if (m_currentKeyframeOriginal.first > -1) {
                    qDebug()<<"=== SETTING CURRENT KEYFRAME: "<<m_currentKeyframe.first;
                    m_moveKeyframeMode = TopMove;
                    if (KdenliveSettings::keyframeseek()) {
536
                        slotSetPosition(m_currentKeyframeOriginal.first, true);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
537
                        emit seekToPos(m_currentKeyframeOriginal.first, getKeyframePosition());
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
                    } else {
                        update();
                    }
                } else {
                    update();
                }
                return;
            }
            // no keyframe next to mouse
            m_selectedKeyframes.clear();
            m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
        } else if (event->y() > m_bottomView + m_lineHeight + 2) {
            // click on zoom area
            if (m_hoverZoom) {
                m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
            }
            return;
555
        } else if (event->y() > m_bottomView - m_lineHeight && event->y() < m_bottomView) {
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
            // click in bottom keyframe area
            if (event->modifiers() & Qt::ShiftModifier) {
                m_clickPoint = pos;
                return;
            }
            int keyframe = getClosestKeyframe(pos, true);
            qDebug()<<"==== KEYFRAME AREA CLICK! CLOSEST KFR: "<<keyframe;
            if (keyframe > -1 && qAbs(keyframe - pos) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
                m_currentKeyframeOriginal = {m_keyframes.key(keyframe),keyframe};
                if (event->modifiers() & Qt::ControlModifier) {
                    if (m_selectedKeyframes.values().contains(m_currentKeyframeOriginal.second)) {
                        m_selectedKeyframes.remove(m_currentKeyframeOriginal.first);
                        m_currentKeyframeOriginal.second = -1;
                    } else {
                        m_selectedKeyframes.insert(m_currentKeyframeOriginal.first, m_currentKeyframeOriginal.second);
                    }
                } else if (!m_selectedKeyframes.values().contains(m_currentKeyframeOriginal.second)) {
                    m_selectedKeyframes = {m_currentKeyframeOriginal};
                }
                // Select and seek to keyframe
                m_currentKeyframe = m_currentKeyframeOriginal;
                std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
                emit selectedKf(m_currentKeyframe, speeds);
                if (m_currentKeyframeOriginal.second > -1) {
                    m_moveKeyframeMode = BottomMove;
                    if (KdenliveSettings::keyframeseek()) {
582
                        slotSetPosition(m_currentKeyframeOriginal.first, true);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
583
                        emit seekToPos(m_currentKeyframeOriginal.first, getKeyframePosition());
584
585
586
587
588
589
590
591
592
593
594
                    } else {
                        update();
                    }
                } else {
                    update();
                }
                return;
            }
            // no keyframe next to mouse
            m_selectedKeyframes.clear();
            m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
595
        } else if (event->y() <= m_lineHeight) {
596
597
598
            qDebug()<<"=== PRESSED WITH Y: "<<event->y() <<" <  "<<(2 * m_lineHeight);
            if (pos != m_position) {
                m_moveKeyframeMode = CursorMove;
599
                slotSetPosition(pos, true);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
600
                emit seekToPos(pos, -1);
601
602
603
                update();
            }
        } else if (event->y() > m_bottomView) {
604
605
606
            int topPos = GenTime(m_remapLink->anim_get_double("map", pos)).frames(pCore->getCurrentFps());
            qDebug()<<"=== PRESSED WITH Y: "<<pos<<" = "<<m_remapLink->anim_get_double("map", pos)<<" TOP: "<<topPos;
            if (topPos != m_position) {
607
                m_moveKeyframeMode = CursorMoveBottom;
608
                slotSetPosition(topPos, true);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
609
                emit seekToPos(-1, pos);
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
                update();
            }
        }
    } else if (event->button() == Qt::RightButton && event->y() > m_bottomView + m_lineHeight) {
        // Right click on zoom, switch between no zoom and last zoom status
        if (m_zoomHandle == QPointF(0, 1)) {
            if (!m_lastZoomHandle.isNull()) {
                m_zoomHandle = m_lastZoomHandle;
                update();
                return;
            }
        } else {
            m_lastZoomHandle = m_zoomHandle;
            m_zoomHandle = QPointF(0, 1);
            update();
            return;
        }
    }
    if (pos != m_position) {
        //emit seekToPos(pos);
        update();
    }
}

634
void RemapView::slotSetPosition(int pos, bool force)
635
636
{
    if (pos != m_position) {
637
638
639
        if (!force && m_moveKeyframeMode != NoMove) {
            return;
        }
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
        m_position = pos;
        //int offset = pCore->getItemIn(m_model->getOwnerId());
        emit atKeyframe(m_keyframes.contains(pos));
        double zoomPos = double(m_position) / m_duration;
        if (zoomPos < m_zoomHandle.x()) {
            double interval = m_zoomHandle.y() - m_zoomHandle.x();
            zoomPos = qBound(0.0, zoomPos - interval / 5, 1.0);
            m_zoomHandle.setX(zoomPos);
            m_zoomHandle.setY(zoomPos + interval);
        } else if (zoomPos > m_zoomHandle.y()) {
            double interval = m_zoomHandle.y() - m_zoomHandle.x();
            zoomPos = qBound(0.0, zoomPos + interval / 5, 1.0);
            m_zoomHandle.setX(zoomPos - interval);
            m_zoomHandle.setY(zoomPos);
        }
        update();
    }
}

void RemapView::goNext()
{
    // insert keyframe at interpolated position
    QMapIterator<int, int> i(m_keyframes);
    while (i.hasNext()) {
        i.next();
        if (i.key() > m_position) {
            m_currentKeyframe = {i.key(),i.value()};
            slotSetPosition(i.key());
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
668
            emit seekToPos(i.key(), getKeyframePosition());
669
670
671
672
673
674
675
676
677
678
679
            std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
            emit selectedKf(m_currentKeyframe, speeds);
            break;
        }
    }
}

void RemapView::goPrev()
{
    // insert keyframe at interpolated position
    bool previousFound = false;
680
681
682
683
684
685
686
    QMap<int, int>::const_iterator it = m_keyframes.constBegin();
    while (it.key() < m_position && it != m_keyframes.constEnd()) {
        it++;
    }
    if (it != m_keyframes.constEnd()) {
        if (it != m_keyframes.constBegin()) {
            it--;
687
        }
688
689
        m_currentKeyframe = {it.key(), it.value()};
        slotSetPosition(m_currentKeyframe.first);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
690
        emit seekToPos(m_currentKeyframe.first, getKeyframePosition());
691
692
693
        std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
        emit selectedKf(m_currentKeyframe, speeds);
        previousFound = true;
694
    }
695

696
697
698
699
    if (!previousFound && !m_keyframes.isEmpty()) {
        // We are after the last keyframe
        m_currentKeyframe = {m_keyframes.lastKey(), m_keyframes.value(m_keyframes.lastKey())};
        slotSetPosition(m_currentKeyframe.first);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
700
        emit seekToPos(m_currentKeyframe.first, getKeyframePosition());
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
        std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
        emit selectedKf(m_currentKeyframe, speeds);
    }
}

void RemapView::updateBeforeSpeed(double speed)
{
    QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first);
    if (*it != m_keyframes.first()) {
        it--;
        int updatedLength = (m_currentKeyframe.first - it.key()) * 100. / speed;
        int offset = it.value() + updatedLength - m_currentKeyframe.second;
        m_currentKeyframe.second = it.value() + updatedLength;
        m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
        // Update all keyframes after that so that we don't alter the speeds
        while (m_moveNext && it != m_keyframes.end()) {
            it++;
            m_keyframes.insert(it.key(), it.value() + offset);
        }
720
        updateKeyframes();
721
722
723
724
725
726
727
728
729
730
731
        update();
    }
}

void RemapView::updateAfterSpeed(double speed)
{
    QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first);
    if (*it != m_keyframes.last()) {
        it++;
        int updatedLength = (it.key() - m_currentKeyframe.first) * 100. / speed;
        m_keyframes.insert(it.key(), m_currentKeyframe.second + updatedLength);
732
        updateKeyframes();
733
734
735
736
        update();
    }
}

737
const QString RemapView::getKeyframesData() const
738
739
740
741
742
{
    QStringList result;
    QMapIterator<int, int> i(m_keyframes);
    while (i.hasNext()) {
        i.next();
743
        result << QString("%1=%2").arg(m_service->frames_to_time(i.value(), mlt_time_clock)).arg(GenTime(i.key(), pCore->getCurrentFps()).seconds());
744
745
746
747
    }
    return result.join(QLatin1Char(';'));
}

748
749
void RemapView::reloadProducer()
{
750
751
    if (!m_clip || !m_clip->clipUrl().endsWith(QLatin1String(".mlt"))) {
        qDebug()<<"==== this is not a playlist clip, aborting";
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
        return;
    }
    Mlt::Consumer c(pCore->getCurrentProfile()->profile(), "xml", m_clip->clipUrl().toUtf8().constData());
    QScopedPointer<Mlt::Service> serv(m_clip->originalProducer()->producer());
    if (serv == nullptr) {
        return;
    }
    qDebug()<<"==== GOR PLAYLIST SERVICE: "<<serv->type()<<" / "<<serv->consumer()->type()<<", SAVING TO "<<m_clip->clipUrl();
    Mlt::Multitrack s2(*serv.data());
    qDebug()<<"==== MULTITRACK: "<<s2.count();
    Mlt::Tractor s(pCore->getCurrentProfile()->profile());
    s.set_track(*s2.track(0), 0);
    qDebug()<<"==== GOT TRACKS: "<<s.count();
    int ignore = s.get_int("ignore_points");
    if (ignore) {
        s.set("ignore_points", 0);
    }
    c.connect(s);
    c.set("time_format", "frames");
    c.set("no_meta", 1);
    c.set("no_root", 1);
    //c.set("no_profile", 1);
    c.set("root", "/");
    c.set("store", "kdenlive");
    c.run();
    if (ignore) {
        s.set("ignore_points", ignore);
    }
}

782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
std::pair<double,double> RemapView::getSpeed(std::pair<int,int>kf)
{
    std::pair<double,double> speeds = {-1,-1};
    QMap<int, int>::const_iterator it = m_keyframes.constFind(kf.first);
    if (it == m_keyframes.constEnd()) {
        // Not a keyframe
        return speeds;
    }
    if (*it != m_keyframes.first()) {
        it--;
        speeds.first = (double)qAbs(kf.first - it.key()) / qAbs(kf.second - it.value());
        it++;
    }
    if (*it != m_keyframes.last()) {
        it++;
        speeds.second = (double)qAbs(kf.first - it.key()) / qAbs(kf.second - it.value());
    }
    return speeds;
}

802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
int RemapView::getKeyframePosition() const
{
    QMapIterator<int, int> i(m_keyframes);
    std::pair<int, int> newKeyframe = {-1,-1};
    std::pair<int, int> previous = {-1,-1};
    newKeyframe.first = m_position;
    while (i.hasNext()) {
        i.next();
        if (i.key() > m_position) {
            if (i.key() == m_keyframes.firstKey()) {
                // This is the first keyframe
                double ratio = (double)m_position / i.key();
                return i.value() * ratio;
                break;
            } else if (previous.first > -1) {
                std::pair<int,int> current = {i.key(), i.value()};
                double ratio = (double)(m_position - previous.first) / (current.first - previous.first);
                return previous.second + (qAbs(current.second - previous.second) * ratio);
                break;
            }
        }
        previous = {i.key(), i.value()};
    }
    if (newKeyframe.second == -1) {
        // We are after the last keyframe
        if (m_keyframes.isEmpty()) {
            return m_position;
        } else {
            double ratio = (double)(m_position - m_keyframes.lastKey()) / (m_duration - m_keyframes.lastKey());
            return m_keyframes.value(m_keyframes.lastKey()) + (qAbs(m_duration - m_keyframes.value(m_keyframes.lastKey())) * ratio);
        }
    }
    return m_position;
}

837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
void RemapView::addKeyframe()
{
    // insert or remove keyframe at interpolated position
    if (m_keyframes.contains(m_position)) {
        m_keyframes.remove(m_position);
        if (m_currentKeyframe.first == m_position) {
            m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
            std::pair<double,double> speeds = {-1,-1};
            emit selectedKf(m_currentKeyframe, speeds);
        }
        emit atKeyframe(false);
        updateKeyframes();
        update();
        return;
    }
    QMapIterator<int, int> i(m_keyframes);
    std::pair<int, int> newKeyframe = {-1,-1};
    std::pair<int, int> previous = {-1,-1};
    newKeyframe.first = m_position;
    while (i.hasNext()) {
        i.next();
        if (i.key() > m_position) {
            if (i.key() == m_keyframes.firstKey()) {
                // This is the first keyframe
                double ratio = (double)m_position / i.key();
                newKeyframe.second = i.value() * ratio;
                break;
            } else if (previous.first > -1) {
                std::pair<int,int> current = {i.key(), i.value()};
                double ratio = (double)(m_position - previous.first) / (current.first - previous.first);
                qDebug()<<"=== RATIO: "<<ratio;
                newKeyframe.second = previous.second + (qAbs(current.second - previous.second) * ratio);
                break;
            }
        }
        previous = {i.key(), i.value()};
    }
    if (newKeyframe.second == -1) {
        // We are after the last keyframe
        if (m_keyframes.isEmpty()) {
            newKeyframe.second = m_position;
        } else {
            double ratio = (double)(m_position - m_keyframes.lastKey()) / (m_duration - m_keyframes.lastKey());
            newKeyframe.second = m_keyframes.value(m_keyframes.lastKey()) + (qAbs(m_duration - m_keyframes.value(m_keyframes.lastKey())) * ratio);
        }
    }
    m_keyframes.insert(newKeyframe.first, newKeyframe.second);
    m_currentKeyframe = newKeyframe;
    std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
    emit selectedKf(newKeyframe, speeds);
    emit atKeyframe(true);
    updateKeyframes();
889
890
891
    update();
}

892
893
894
895
void RemapView::toggleMoveNext(bool moveNext)
{
    m_moveNext = moveNext;
}
896

897
898
899
900
901
902
903
904
905
906
907
void RemapView::refreshOnDurationChanged(int remapDuration)
{
    if (remapDuration > m_duration) {
        m_duration = remapDuration;
        int maxWidth = width() - (2 * m_offset);
        m_scale = maxWidth / double(qMax(1, m_duration - 1));
        m_zoomStart = m_zoomHandle.x() * maxWidth;
        m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
    }
}

908
909
910
911
912
913
914
915
916
917
void RemapView::resizeEvent(QResizeEvent *event)
{
    int maxWidth = width() - (2 * m_offset);
    m_scale = maxWidth / double(qMax(1, m_duration - 1));
    m_zoomStart = m_zoomHandle.x() * maxWidth;
    m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
    QWidget::resizeEvent(event);
    update();
}

918
919
920
921
922
923
924
void RemapView::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)
    QPalette pal = palette();
    KColorScheme scheme(pal.currentColorGroup(), KColorScheme::Window);
    m_colSelected = palette().highlight().color();
    m_colKeyframe = scheme.foreground(KColorScheme::NormalText).color();
925
    QColor bg = scheme.background(KColorScheme::AlternateBackground ).color();
926
    QStylePainter p(this);
927
928
    int maxWidth = width() - (2 * m_offset);
    int zoomEnd = qCeil(m_zoomHandle.y() * maxWidth);
929
    // Top timeline
930
    p.fillRect(m_offset, 0, maxWidth + 1, m_lineHeight, bg);
931
    // Bottom timeline
932
    p.fillRect(m_offset, m_bottomView, maxWidth + 1, m_lineHeight, bg);
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
    /* ticks */
    double fps = pCore->getCurrentFps();
    int displayedLength = int(m_duration / m_zoomFactor / fps);
    double factor = 1;
    if (displayedLength < 2) {
        // 1 frame tick
    } else if (displayedLength < 30 ) {
        // 1 sec tick
        factor = fps;
    } else if (displayedLength < 150) {
        // 5 sec tick
        factor = 5 * fps;
    } else if (displayedLength < 300) {
        // 10 sec tick
        factor = 10 * fps;
    } else if (displayedLength < 900) {
        // 30 sec tick
        factor = 30 * fps;
    } else if (displayedLength < 1800) {
        // 1 min. tick
        factor = 60 * fps;
    } else if (displayedLength < 9000) {
        // 5 min tick
        factor = 300 * fps;
    } else if (displayedLength < 18000) {
        // 10 min tick
        factor = 600 * fps;
    } else {
        // 30 min tick
        factor = 1800 * fps;
    }

    // Position of left border in frames
    double tickOffset = m_zoomStart * m_zoomFactor;
    double frameSize = factor * m_scale * m_zoomFactor;
    int base = int(tickOffset / frameSize);
    tickOffset = frameSize - (tickOffset - (base * frameSize));
    // Draw frame ticks
    int scaledTick = 0;
    for (int i = 0; i < maxWidth / frameSize; i++) {
973
974
        scaledTick = int(m_offset + (i * frameSize) + tickOffset);
        if (scaledTick >= maxWidth + m_offset) {
975
976
977
            break;
        }
        p.drawLine(QPointF(scaledTick , m_lineHeight + 1), QPointF(scaledTick, m_lineHeight - 3));
978
        p.drawLine(QPointF(scaledTick , m_bottomView + 1), QPointF(scaledTick, m_bottomView - 3));
979
980
981
982
983
    }

    p.setPen(palette().dark().color());

    /*
984
985
     * Time-"lines"
     * We have a top timeline for the source (clip monitor) and a bottom timeline for the output (project monitor)
986
987
     */
    p.setPen(m_colKeyframe);
988
    // Top timeline
989
990
    qDebug()<<"=== MAX KFR WIDTH: "<<maxWidth<<", DURATION SCALED: "<<(m_duration * m_scale)<<", POS: "<<(m_position * m_scale);
    p.drawLine(m_offset, m_lineHeight, maxWidth + m_offset, m_lineHeight);
991
    p.drawLine(m_offset, m_lineHeight - m_lineHeight / 4, m_offset, m_lineHeight + m_lineHeight / 4);
992
    p.drawLine(maxWidth + m_offset, m_lineHeight - m_lineHeight / 4, maxWidth + m_offset, m_lineHeight + m_lineHeight / 4);
993
    // Bottom timeline
994
    p.drawLine(m_offset, m_bottomView, maxWidth + m_offset, m_bottomView);
995
    p.drawLine(m_offset, m_bottomView - m_lineHeight / 4, m_offset, m_bottomView + m_lineHeight / 4);
996
    p.drawLine(maxWidth + m_offset, m_bottomView - m_lineHeight / 4, maxWidth + m_offset, m_bottomView + m_lineHeight / 4);
997
    /*
998
     * Keyframes
999
     */
1000
1001
1002
1003
1004
1005
    QMapIterator<int, int> i(m_keyframes);
    while (i.hasNext()) {
        i.next();
        double inPos = (double)i.key() * m_scale;
        double outPos = (double)i.value() * m_scale;
        if ((inPos < m_zoomStart && outPos < m_zoomStart) || (qFloor(inPos) > zoomEnd && qFloor(outPos) > zoomEnd)) {
1006
            qDebug()<<"=== KEYFRAME OUTSIDE: "<<inPos<<"x"<<outPos<<", ZOOM ST: "<<m_zoomStart<<"x"<<zoomEnd;
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
            continue;
        }
        if ((m_hoverKeyframe.second == false && m_hoverKeyframe.first == i.key()) || (m_hoverKeyframe.second == true && m_hoverKeyframe.first == i.value())) {
            p.setPen(Qt::red);
            p.setBrush(Qt::darkRed);
        } else if (m_selectedKeyframes.contains(i.key()) || m_currentKeyframe.first == i.key() || m_currentKeyframe.second == i.value()) {
            p.setPen(m_colSelected);
            p.setBrush(m_colSelected);
        } else {
            p.setPen(m_colKeyframe);
            p.setBrush(m_colKeyframe);
        }
        inPos -= m_zoomStart;
        inPos *= m_zoomFactor;
        inPos += m_offset;
        outPos -= m_zoomStart;
        outPos *= m_zoomFactor;
        outPos += m_offset;
1025

1026
1027
1028
1029
1030
        p.drawLine(inPos, m_lineHeight + m_lineHeight * 0.75, outPos, m_bottomView - m_lineHeight * 0.75);
        p.drawLine(inPos, m_lineHeight, inPos, m_lineHeight + m_lineHeight / 2);
        p.drawLine(outPos, m_bottomView, outPos, m_bottomView - m_lineHeight / 2);
        p.drawEllipse(QRectF(inPos - m_lineHeight / 4.0, m_lineHeight + m_lineHeight / 2, m_lineHeight / 2, m_lineHeight / 2));
        p.drawEllipse(QRectF(outPos - m_lineHeight / 4.0, m_bottomView - m_lineHeight, m_lineHeight / 2, m_lineHeight / 2));
1031
1032
1033
1034
1035
    }

    /*
     * current position cursor
     */
1036
    p.setPen(m_colSelected);
1037
    if (m_position >= 0 && m_position < m_duration) {
1038
        p.setBrush(m_colSelected);
1039
        double scaledPos = m_position * m_scale;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1040
1041
1042
1043
        scaledPos -= m_zoomStart;
        scaledPos *= m_zoomFactor;
        scaledPos += m_offset;
        if (scaledPos >= m_offset && qFloor(scaledPos) <= m_offset + maxWidth) {
1044
1045
1046
1047
            QPolygonF topCursor;
            topCursor << QPointF(-int(m_lineHeight / 3), -m_lineHeight * 0.5) << QPointF(int(m_lineHeight / 3), -m_lineHeight * 0.5) << QPointF(0, 0);
            topCursor.translate(scaledPos, m_lineHeight);
            p.drawPolygon(topCursor);
1048
1049
1050
        }
        int projectPos = getKeyframePosition();
        double scaledPos2 = projectPos * m_scale;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1051
1052
1053
1054
        scaledPos2 -= m_zoomStart;
        scaledPos2 *= m_zoomFactor;
        scaledPos2 += m_offset;
        if (scaledPos2 >= m_offset && qFloor(scaledPos2) <= m_offset + maxWidth) {
1055
1056
1057
            QPolygonF bottomCursor;
            bottomCursor << QPointF(-int(m_lineHeight / 3), m_lineHeight * 0.5) << QPointF(int(m_lineHeight / 3), m_lineHeight * 0.5) << QPointF(0, 0);
            bottomCursor.translate(scaledPos2, m_bottomView);
1058
            p.setBrush(m_colSelected);
1059
            p.drawPolygon(bottomCursor  );
1060
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1061
1062
1063
        p.drawLine(scaledPos, m_lineHeight * 1.75, scaledPos2, m_bottomView - (m_lineHeight * 0.75));
        p.drawLine(scaledPos, m_lineHeight, scaledPos, m_lineHeight * 1.75);
        p.drawLine(scaledPos2, m_bottomView, scaledPos2, m_bottomView - m_lineHeight * 0.75);
1064
    }
1065
1066

    // Zoom bar
1067
    p.setPen(Qt::NoPen);
1068
    p.setBrush(palette().mid());
1069
    p.drawRoundedRect(0, m_bottomView + m_lineHeight + 4, width() - 2 * 0, m_zoomHeight, m_lineHeight / 3, m_lineHeight / 3);
1070
1071
    p.setBrush(palette().highlight());
    p.drawRoundedRect(int((width()) * m_zoomHandle.x()),
1072
                      m_bottomView + m_lineHeight + 4,
1073
                      int((width()) * (m_zoomHandle.y() - m_zoomHandle.x())),
1074
1075
                      m_zoomHeight,
                      m_lineHeight / 3, m_lineHeight / 3);
1076
1077
1078
1079
1080
}


TimeRemap::TimeRemap(QWidget *parent)
    : QWidget(parent)
1081
    , m_cid(-1)
1082
1083
1084
1085
1086
1087
1088
1089
1090
{
    setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
    setupUi(this);

    m_in = new TimecodeDisplay(pCore->timecode(), this);
    inLayout->addWidget(m_in);
    m_out = new TimecodeDisplay(pCore->timecode(), this);
    outLayout->addWidget(m_out);
    m_view = new RemapView(this);
1091
1092
    time_box->setEnabled(false);
    speed_box->setEnabled(false);
1093
    remapLayout->addWidget(m_view);
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
    connect(m_view, &RemapView::selectedKf, [this](std::pair<int,int>selection, std::pair<double,double>speeds) {
        qDebug()<<"=== SELECTED KFR SPEEDS: "<<speeds;
        time_box->setEnabled(true);
        speed_box->setEnabled(true);
        QSignalBlocker bk(m_in);
        QSignalBlocker bk2(m_out);
        m_in->setEnabled(selection.first >= 0);
        m_out->setEnabled(selection.first >= 0);
        m_in->setValue(selection.first);
        m_out->setValue(selection.second);
        QSignalBlocker bk3(speedBefore);
        QSignalBlocker bk4(speedAfter);
        speedBefore->setEnabled(speeds.first > 0);
        speedBefore->setValue(100. * speeds.first);
        speedAfter->setEnabled(speeds.second > 0);
        speedAfter->setValue(100. * speeds.second);
    });
    connect(m_view, &RemapView::updateKeyframes, this, &TimeRemap::updateKeyframes);
    connect(m_in, &TimecodeDisplay::timeCodeUpdated, [this]() {
        m_view->updateInPos(m_in->getValue());
    });
    connect(m_out, &TimecodeDisplay::timeCodeUpdated, [this]() {
        m_view->updateOutPos(m_out->getValue());
    });
    connect(m_view, &RemapView::atKeyframe, button_add, [&](bool atKeyframe) {
        button_add->setIcon(atKeyframe ? QIcon::fromTheme(QStringLiteral("list-remove")) : QIcon::fromTheme(QStringLiteral("list-add")));
    });
    connect(speedBefore, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [&](double speed) {
        m_view->updateBeforeSpeed(speed);
    });
    connect(speedAfter, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [&](double speed) {
        m_view->updateAfterSpeed(speed);
    });
    connect(button_add, &QToolButton::clicked, m_view, &RemapView::addKeyframe);
    connect(button_next, &QToolButton::clicked, m_view, &RemapView::goNext);
    connect(button_prev, &QToolButton::clicked, m_view, &RemapView::goPrev);
    connect(move_next, &QCheckBox::toggled, m_view, &RemapView::toggleMoveNext);
1131
    connect(m_view, &RemapView::updateMaxDuration, [this](int duration) {
1132
1133
        m_out->setRange(m_out->minimum(), INT_MAX);
        m_in->setRange(m_in->minimum(), duration - 1);
1134
    });
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1135
    setEnabled(false);
1136
1137
}

1138
void TimeRemap::selectedClip(int cid)
1139
1140
1141
1142
1143
1144
{
    if (cid == -1 && cid == m_cid) {
        return;
    }
    QObject::disconnect( m_seekConnection1 );
    QObject::disconnect( m_seekConnection2 );
1145
    QObject::disconnect( m_seekConnection3 );
1146
    m_cid = cid;
1147
    qDebug()<<"======\n\n!!!!!!!!!!  SELECTED CLIP: "<<m_cid<<"\n\n==========";
1148
1149
1150
1151
1152
    if (cid == -1) {
        m_view->setDuration(nullptr, 0);
        setEnabled(false);
        return;
    }
1153
    m_view->m_remapLink.reset();
1154
1155
    bool keyframesLoaded = false;
    std::shared_ptr<TimelineItemModel> model = pCore->window()->getCurrentTimeline()->controller()->getModel();
1156
1157
    model->requestClipTimeRemap(cid);
    m_splitId = model->m_groups->getSplitPartner(cid);
1158
    const QString binId = pCore->getTimelineClipBinId(cid);
1159
1160
1161
    int min = pCore->getItemIn({ObjectType::TimelineClip,cid});
    m_lastLength = pCore->getItemDuration({ObjectType::TimelineClip,cid});
    int max = min + m_lastLength;
1162
    pCore->selectBinClip(binId, true, min, {min,max});
1163
    m_view->m_startPos = pCore->getItemPosition({ObjectType::TimelineClip,cid});
1164
1165
    m_in->setRange(min, max - 1);
    m_out->setRange(min, INT_MAX);
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
    std::shared_ptr<Mlt::Producer> prod = model->getClipProducer(cid);
    m_view->setDuration(prod, max - min);
    qDebug()<<"===== GOT PRODUCER TYPE: "<<prod->parent().type();
    if (prod->parent().type() == mlt_service_chain_type) {
        Mlt::Chain fromChain(prod->parent());
        int count = fromChain.link_count();
        for (int i = 0; i < count; i++) {
            QScopedPointer<Mlt::Link> fromLink(fromChain.link(i));
            if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) {
                if (fromLink->get("mlt_service") == QLatin1String("timeremap")) {
                    // Found a timeremap effect, read params
1177
                    m_view->m_remapLink = std::make_shared<Mlt::Link>(fromChain.link(i)->get_link());
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
                    if (m_splitId > -1) {
                        std::shared_ptr<Mlt::Producer> prod2 = model->getClipProducer(m_splitId);
                        if (prod2->parent().type() == mlt_service_chain_type) {
                            Mlt::Chain fromChain2(prod2->parent());
                            count = fromChain2.link_count();
                            for (int j = 0; j < count; j++) {
                                QScopedPointer<Mlt::Link> fromLink2(fromChain2.link(j));
                                if (fromLink2 && fromLink2->is_valid() && fromLink2->get("mlt_service")) {
                                    if (fromLink2->get("mlt_service") == QLatin1String("timeremap")) {
                                        m_splitRemap = std::make_shared<Mlt::Link>(fromChain2.link(j)->get_link());
                                    }
                                }
                            }
                        }
                    }
                    QString mapData(fromLink->get("map"));
                    m_view->loadKeyframes(mapData);
                    keyframesLoaded = true;
                    setEnabled(true);
                    break;
                }
            }
        }
    }
1202
1203
1204
    m_seekConnection3 = connect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekPosition, [this](int pos) {
        m_view->slotSetPosition(pos);
    });
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1205
1206
1207
1208
1209
1210
1211
    m_seekConnection1 = connect(m_view, &RemapView::seekToPos, [this](int topPos, int bottomPos) {
        if (topPos > -1 && source_seek->isChecked()) {
            pCore->getMonitor(Kdenlive::ClipMonitor)->requestSeek(topPos);
        }
        if (bottomPos > -1 && output_seek->isChecked()) {
            pCore->getMonitor(Kdenlive::ProjectMonitor)->requestSeek(bottomPos + m_view->m_startPos);
        }
1212
    });
1213
    m_seekConnection2 = connect(pCore->getMonitor(Kdenlive::ProjectMonitor), &Monitor::seekPosition, [this](int pos) {
1214
1215
        qDebug()<<"=== PROJECT SEEK: "<<pos<<", START: "<<m_view->m_startPos<<", MAPPED: "<<GenTime(m_view->m_remapLink->anim_get_double("map", pos - m_view->m_startPos)).frames(pCore->getCurrentFps());
        m_view->slotSetPosition(GenTime(m_view->m_remapLink->anim_get_double("map", pos - m_view->m_startPos)).frames(pCore->getCurrentFps()));
1216
1217
1218
    });
}

1219
1220
void TimeRemap::setClip(std::shared_ptr<ProjectClip> clip, int in, int out)
{
1221
1222
1223
    if (m_cid > -1 && clip == nullptr) {
        return;
    }
1224
1225
    QObject::disconnect( m_seekConnection1 );
    QObject::disconnect( m_seekConnection2 );
1226
    QObject::disconnect( m_seekConnection3 );
1227
    m_cid = -1;
1228
    if (clip == nullptr || !clip->statusReady() || clip->clipType() != ClipType::Playlist) {
1229
        m_view->setDuration(nullptr, 0);
1230
1231
1232
        setEnabled(false);
        return;
    }
1233
    m_view->m_remapLink.reset();
1234
    bool keyframesLoaded = false;
1235
    if (clip != nullptr) {
1236
        int min = in == -1 ? 0 : in;
1237
        int max = out == -1 ? clip->getFramePlaytime() : out;
1238
        m_in->setRange(min, max);
1239
        m_out->setRange(min, INT_MAX);
1240
        m_view->m_startPos = 0;
1241
        m_view->setBinClipDuration(clip, max - min);
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
        if (clip->clipType() == ClipType::Playlist) {
            Mlt::Service service(clip->originalProducer()->producer()->get_service());
            qDebug()<<"==== producer type: "<<service.type();
            if (service.type() == mlt_service_multitrack_type) {
                Mlt::Multitrack multi(service);
                for (int i = 0; i < multi.count(); i++) {
                    std::unique_ptr<Mlt::Producer> track(multi.track(i));
                    qDebug()<<"==== GOT TRACK TYPE: "<<track->type();
                    switch (track->type()) {
                        case mlt_service_chain_type: {
                            Mlt::Chain fromChain(*track.get());
                            int count = fromChain.link_count();
                            for (int i = 0; i < count; i++) {
                                QScopedPointer<Mlt::Link> fromLink(fromChain.link(i));
                                if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) {
                                    if (fromLink->get("mlt_service") == QLatin1String("timeremap")) {
                                        // Found a timeremap effect, read params
1259
                                        m_view->m_remapLink = std::make_shared<Mlt::Link>(fromChain.link(i)->get_link());
1260
                                        QString mapData(fromLink->get("map"));
1261
                                        m_view->loadKeyframes(mapData);
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
                                        keyframesLoaded = true;
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                        case mlt_service_playlist_type: {
                            // that is a single track
                            Mlt::Playlist local_playlist(*track);
                            int max = local_playlist.count();
                            qDebug()<<"==== PLAYLIST COUNT: "<<max;
                            if (max == 1) {
                                Mlt::Producer prod = local_playlist.get_clip(0)->parent();
                                qDebug()<<"==== GOT PROD TYPE: "<<prod.type()<<" = "<<prod.get("mlt_service")<<" = "<<prod.get("resource");
                                if (prod.type() == mlt_service_chain_type) {
                                    Mlt::Chain fromChain(prod);
                                    int count = fromChain.link_count();
                                    for (int i = 0; i < count; i++) {
                                        QScopedPointer<Mlt::Link> fromLink(fromChain.link(i));
                                        if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) {
                                            if (fromLink->get("mlt_service") == QLatin1String("timeremap")) {
                                                // Found a timeremap effect, read params
1285
                                                m_view->m_remapLink = std::make_shared<Mlt::Link>(fromChain.link(i)->get_link());
1286
                                                QString mapData(fromLink->get("map"));
1287
                                                m_view->loadKeyframes(mapData);
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
                                                keyframesLoaded = true;
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                            break;
                        }
                        default:
                            qDebug()<<"=== UNHANDLED TRACK TYPE";
                            break;
                    }
                }
            }
        }
        if (!keyframesLoaded) {
1305
            m_view->loadKeyframes(QString());
1306
        }
1307
        m_seekConnection1 = connect(m_view, &RemapView::seekToPos, pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::requestSeek, Qt::UniqueConnection);
1308
1309
1310
1311
        m_seekConnection2 = connect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekPosition, [&](int pos) {
            m_view->slotSetPosition(pos);
        });
        setEnabled(m_view->m_remapLink != nullptr);
1312
1313
1314
1315
1316
    } else {
        setEnabled(false);
    }
}

1317
1318
void TimeRemap::updateKeyframes()
{
1319
    QString kfData = m_view->getKeyframesData();
1320
    qDebug()<<"SAME DURATION: "<<m_lastLength<<  "= "<<m_view->remapDuration()<<", CID: "<<m_cid;
1321
    if (m_view->m_remapLink) {
1322
        qDebug()<<"====== OK; PROCESSING REMAP UPDATE";
1323
        m_view->m_remapLink->set("map", kfData.toUtf8().constData());
1324
1325
1326
1327
1328
1329
1330
1331
1332
        if (m_splitRemap) {
            m_splitRemap->set("map", kfData.toUtf8().constData());
        }
        if (m_cid == -1) {
            // This is a playlist clip
            m_view->timer.start();
        } else if (m_lastLength != m_view->remapDuration()) {
            // Resize timeline clip
            m_lastLength = m_view->remapDuration();
1333
            m_view->refreshOnDurationChanged(m_lastLength);
1334
1335
1336
            std::shared_ptr<TimelineItemModel> model = pCore->window()->getCurrentTimeline()->controller()->getModel();
            model->requestItemResize(m_cid, m_lastLength, true, true, -1, false);
        }
1337
1338
1339
    }
}

1340
1341
1342
1343
TimeRemap::~TimeRemap()
{
    //delete m_previewTimer;
}