timeremap.cpp 64.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
/***************************************************************************
 *   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"
24
#include "timecodedisplay.h"
25
26
27
#include "kdenlivesettings.h"
#include "bin/projectclip.h"
#include "project/projectmanager.h"
28
#include "monitor/monitor.h"
29
30
31
32
#include "profiles/profilemodel.hpp"
#include "mainwindow.h"
#include "timeline2/view/timelinewidget.h"
#include "timeline2/view/timelinecontroller.h"
33
#include "timeline2/model/groupsmodel.hpp"
34
#include "macros.hpp"
35
36
37
38
39

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

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

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

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;
88
        updateKeyframes(true);
89
90
91
92
93
94
95
96
97
98
99
100
101
        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;
102
        updateKeyframes(true);
103
104
        update();
    }
105
106
}

107
108
109
110
111
112
113
114
115
116
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();
        }
    }
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
    return maxDuration - m_inFrame + 1;
}

int RemapView::remapMax() const
{
    int maxDuration = 0;
    QMapIterator<int, int> i(m_keyframes);
    while (i.hasNext()) {
        i.next();
        if (i.value() > maxDuration) {
            maxDuration = i.value();
        }
        if (i.key() > maxDuration) {
            maxDuration = i.key();
        }
    }
    return maxDuration - m_inFrame + 1;
}

bool RemapView::movingKeyframe() const
{
    return m_moveKeyframeMode == BottomMove;
139
140
141
}

void RemapView::setBinClipDuration(std::shared_ptr<ProjectClip> clip, int duration)
142
{
143
    m_clip = clip;
144
145
    m_service = clip->originalProducer();
    m_duration = duration;
146
147
    int maxWidth = width() - (2 * m_offset);
    m_scale = maxWidth / double(qMax(1, m_duration - 1));
148
149
150
151
152
153
    m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
}

void RemapView::setDuration(std::shared_ptr<Mlt::Producer> service, int duration)
{
    m_clip = nullptr;
154
155
156
157
158
159
160
161
162
163
164
    if (duration < 0) {
        // reset
        m_service = nullptr;
        m_inFrame = 0;
        m_duration = -1;
        return;
    }
    if (service) {
        m_service = service;
    }
    bool keyframeAdded = false;
165
    if (m_duration > 0 && m_service && !m_keyframes.isEmpty()) {
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
        if (duration > m_duration) {
            // The clip was resized, ensure we have a keyframe at the end of the clip will freeze at last keyframe
            QMap<int, int>::const_iterator it = m_keyframes.constEnd();
            it--;
            int lastKeyframePos = it.key();
            int lastKeyframeValue = it.value();
            int lastPos = m_inFrame + duration - 1;
            if (lastPos > lastKeyframePos) {
                keyframeAdded = true;
                std::pair<double,double>speeds = getSpeed({lastKeyframePos,lastKeyframeValue});
                if (lastKeyframePos == m_inFrame + m_duration - 1) {
                    // Move last keyframe
                    //TODO: check we still have a keyframe at it pos
                    it--;
                    int updatedVal = it.value() + ((lastPos - it.key()) / speeds.first);
                    m_keyframes.remove(lastKeyframePos);
                    while (m_keyframes.values().contains(updatedVal)) {
                        updatedVal++;
                    }
                    m_keyframes.insert(lastPos, updatedVal);
                } else {
                    // Add a keyframe at end
                    int updatedVal = it.value() + (lastPos - it.key());
                    while (m_keyframes.values().contains(updatedVal)) {
                        updatedVal++;
                    }
                    m_keyframes.insert(lastPos, updatedVal);
                }
            }
        } else if (duration < m_duration) {
            keyframeAdded = true;
            QMap<int, int>::const_iterator it = m_keyframes.constEnd();
            it--;
            int lastKeyframePos = it.key();
            int lastKeyframeValue = it.value();
            int lastPos = m_inFrame + duration - 1;
            std::pair<double,double>speeds = getSpeed({lastKeyframePos,lastKeyframeValue});
            if (lastKeyframePos == m_inFrame + m_duration - 1) {
                // Move last keyframe
                it--;
                //TODO: check we still have a keyframe at it pos
                int updatedVal = it.value() + ((lastPos - it.key()) / speeds.first);
                m_keyframes.remove(lastKeyframePos);
                while (m_keyframes.values().contains(updatedVal)) {
                    updatedVal++;
                }
                m_keyframes.insert(lastPos, updatedVal);
            }
        }
    }
    if (service == nullptr) {
        // We are updating an existing remap effect due to duration change
        m_duration = qMax(duration, remapMax());
    } else {
        m_duration = duration;
    }
    if (service != nullptr) {
        m_inFrame = m_service->get_in();
    }
225
226
    int maxWidth = width() - (2 * m_offset);
    m_scale = maxWidth / double(qMax(1, m_duration - 1));
227
228
    m_zoomStart = m_zoomHandle.x() * maxWidth;
    m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
229
    m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
230
231
232
    if (keyframeAdded) {
        updateKeyframes(false);
    }
233
234
}

235
void RemapView::loadKeyframes(const QString &mapData)
236
237
238
{
    m_keyframes.clear();
    if (mapData.isEmpty()) {
239
240
241
242
243
244
        if (m_inFrame > 0) {
            m_keyframes.insert(0, 0);
        }
        m_keyframes.insert(m_inFrame, m_inFrame);
        m_keyframes.insert(m_inFrame + m_duration - 1, m_inFrame + m_duration - 1);
        updateKeyframes(true);
245
246
247
    } else {
        QStringList str = mapData.split(QLatin1Char(';'));
        for (auto &s : str) {
248
            int pos = m_service->time_to_frames(s.section(QLatin1Char('='), 0, 0).toUtf8().constData());
249
250
            int val = GenTime(s.section(QLatin1Char('='), 1).toDouble()).frames(pCore->getCurrentFps());
            m_keyframes.insert(val, pos);
251
252
            m_duration = qMax(m_duration, pos - m_inFrame);
            m_duration = qMax(m_duration, val - m_inFrame);
253
        }
254
255
256
257
        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);
258
        emit updateMaxDuration(m_duration);
259
260
261
262
263
264
265
266
267
        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});
        }
268
269
270
271
272
273
274
275
276
277
278
    }
    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);
279
    int realPos = qMax(m_inFrame, pos + m_inFrame);
280
281
    pos = qBound(0, pos, m_duration - 1);
    GenTime position(pos, pCore->getCurrentFps());
282
283
284
285
    if (event->buttons() == Qt::NoButton) {
        bool hoverKeyframe = false;
        if (event->y() > m_lineHeight && event->y() < 2 * m_lineHeight) {
            // mouse click in top keyframes area
286
287
            int keyframe = getClosestKeyframe(pos + m_inFrame);
            if (keyframe > -1 && qAbs(keyframe - pos - m_inFrame) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
288
289
290
291
                hoverKeyframe = true;
            }
        } else if (event->y() > m_bottomView - m_lineHeight && event->y() < m_bottomView) {
            // click in bottom keyframe area
292
293
            int keyframe = getClosestKeyframe(pos + m_inFrame, true);
            if (keyframe > -1 && qAbs(keyframe - pos - m_inFrame) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
294
295
296
297
298
299
300
301
302
                hoverKeyframe = true;
            }
        }
        if (hoverKeyframe) {
            setCursor(Qt::PointingHandCursor);
        } else {
            setCursor(Qt::ArrowCursor);
        }
    } else if ((event->buttons() & Qt::LeftButton) != 0u) {
303
304
305
306
        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
307
308
309
310
                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);
311
312
313
314
315
                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
316
317
318
319
                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);
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
                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
339
340
341
342
                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);
343
344
345
346
347
                update();
            }
            return;
        }
        //qDebug()<<"=== MOVING MOUSE: "<<pos<<" = "<<m_currentKeyframe<<", MOVE KFMODE: "<<m_moveKeyframeMode;
348
        if ((m_currentKeyframe.first == pos + m_inFrame && m_moveKeyframeMode == TopMove) || (m_currentKeyframe.second == pos + m_inFrame && m_moveKeyframeMode == BottomMove)) {
349
350
351
352
353
            return;
        }
        if (m_currentKeyframe.first >= 0 && m_moveKeyframeMode != NoMove) {
            if (m_moveKeyframeMode == TopMove) {
                // Moving top keyframe
354
355
                if (!m_keyframes.contains(pos + m_inFrame)) {
                    int delta = pos + m_inFrame - m_currentKeyframe.first;
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
                    // 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) {
376
                            m_currentKeyframe.first = pos + m_inFrame;
377
378
379
380
381
                            std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
                            emit selectedKf(m_currentKeyframe, speeds);
                        }
                    }
                    m_selectedKeyframes = updated;
382
383
384
                    updateKeyframes(true);
                    slotSetPosition(pos + m_inFrame);
                    emit seekToPos(pos + m_inFrame, -1);
385
                    return;
386
387
388
                } else {
                    qDebug()<<"=== KEYFRAME :"<< pos<<" ALREADY EXISTS";
                }
389
            } else if (m_moveKeyframeMode == BottomMove) {
390
391
                // Moving bottom keyframe
                auto kfrValues = m_keyframes.values();
392
                //pos = GenTime(m_remapLink->anim_get_double("map", pos)).frames(pCore->getCurrentFps());
393
394
                if (!kfrValues.contains(realPos)) {
                    int delta = realPos - m_currentKeyframe.second;
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
                    // 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) {
413
                            m_currentKeyframe.second = realPos;
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
                        }
                    }
                    // 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;
429
                    updateKeyframes(true);
430
                    emit seekToPos(-1, pos);
431
                    update();
432
                    return;
433
434
435
436
437
                }
            }
        }
        // Rubberband selection
        if (m_clickPoint >= 0) {
438
            m_clickEnd = pos + m_inFrame;
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
            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;
        }

459
        if (m_moveKeyframeMode == CursorMove || (event->y() < 2 * m_lineHeight)) {
460
            if (pos != m_position) {
461
462
                slotSetPosition(pos + m_inFrame);
                emit seekToPos(pos + m_inFrame, -1);
463
464
465
            }
        }
        if (m_moveKeyframeMode == CursorMoveBottom || (event->y() > m_bottomView)) {
466
467
            pos = GenTime(m_remapLink->anim_get_double("map", pos + m_inFrame)).frames(pCore->getCurrentFps());
            if (pos != getKeyframePosition() + m_inFrame) {
468
                slotSetPosition(pos);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
469
                emit seekToPos(-1, getKeyframePosition());
470
471
472
473
474
            }
        }
        return;
    }
    if (event->y() < m_lineHeight) {
475
        int closest = getClosestKeyframe(pos + m_inFrame);
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
        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
513
        int closest = getClosestKeyframe(pos + m_inFrame, true);
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
        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();
    }
}

535
536
537
538
539
int RemapView::position() const
{
    return m_position;
}

540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
void RemapView::centerCurrentKeyframe()
{
    if (m_currentKeyframe.first == -1) {
        // No keyframe selected, abort
        return;
    }
    m_currentKeyframe.second = m_position + m_inFrame;
    m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second);
    std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
    emit selectedKf(m_currentKeyframe, speeds);
    emit atKeyframe(true);
    updateKeyframes(true);
    update();
}

555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
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;
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
    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"));
    }
607
608
609
610
611
612
613
614
615
616
617
618
    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;
619
    m_keyframesOrigin = m_keyframes;
620
    if (event->button() == Qt::LeftButton) {
621
        if (event->y() > m_lineHeight && event->y() < 2 * m_lineHeight) {
622
623
            // mouse click in top keyframes area
            if (event->modifiers() & Qt::ShiftModifier) {
624
                m_clickPoint = pos + m_inFrame;
625
626
                return;
            }
627
            int keyframe = getClosestKeyframe(pos + m_inFrame);
628
            qDebug()<<"==== KEYFRAME AREA CLICK! CLOSEST KFR: "<<keyframe;
629
            if (keyframe > -1 && qAbs(keyframe - (pos + m_inFrame)) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
                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()) {
650
                        slotSetPosition(m_currentKeyframeOriginal.first);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
651
                        emit seekToPos(m_currentKeyframeOriginal.first, getKeyframePosition());
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
                    } 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;
669
        } else if (event->y() > m_bottomView - m_lineHeight && event->y() < m_bottomView) {
670
671
            // click in bottom keyframe area
            if (event->modifiers() & Qt::ShiftModifier) {
672
                m_clickPoint = pos + m_inFrame;
673
674
                return;
            }
675
            int keyframe = getClosestKeyframe(pos + m_inFrame, true);
676
            qDebug()<<"==== KEYFRAME AREA CLICK! CLOSEST KFR: "<<keyframe;
677
            if (keyframe > -1 && qAbs(keyframe - (pos + m_inFrame)) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
                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()) {
696
                        slotSetPosition(m_currentKeyframeOriginal.first);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
697
                        emit seekToPos(m_currentKeyframeOriginal.first, getKeyframePosition());
698
699
700
701
702
703
704
705
706
707
708
                    } else {
                        update();
                    }
                } else {
                    update();
                }
                return;
            }
            // no keyframe next to mouse
            m_selectedKeyframes.clear();
            m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
709
        } else if (event->y() <= m_lineHeight) {
710
711
712
            qDebug()<<"=== PRESSED WITH Y: "<<event->y() <<" <  "<<(2 * m_lineHeight);
            if (pos != m_position) {
                m_moveKeyframeMode = CursorMove;
713
714
                slotSetPosition(pos + m_inFrame);
                emit seekToPos(pos + m_inFrame, -1);
715
716
717
                update();
            }
        } else if (event->y() > m_bottomView) {
718
719
720
            int topPos = GenTime(m_remapLink->anim_get_double("map", pos + m_inFrame)).frames(pCore->getCurrentFps());
            qDebug()<<"==== TOPOS: "<<topPos<<", FOR: "<<pos;
            if (topPos != m_position + m_inFrame) {
721
                m_moveKeyframeMode = CursorMoveBottom;
722
                slotSetPosition(topPos);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
723
                emit seekToPos(-1, pos);
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
                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();
    }
}

748
void RemapView::slotSetPosition(int pos)
749
{
750
751
    if (pos != m_position + m_inFrame) {
        m_position = pos - m_inFrame;
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
        //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();
776
        if (i.key() > m_position + m_inFrame) {
777
778
            m_currentKeyframe = {i.key(),i.value()};
            slotSetPosition(i.key());
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
779
            emit seekToPos(i.key(), getKeyframePosition());
780
781
782
783
784
785
786
787
788
789
790
            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;
791
    QMap<int, int>::const_iterator it = m_keyframes.constBegin();
792
    while (it.key() < m_position + m_inFrame && it != m_keyframes.constEnd()) {
793
794
795
796
797
        it++;
    }
    if (it != m_keyframes.constEnd()) {
        if (it != m_keyframes.constBegin()) {
            it--;
798
        }
799
800
        m_currentKeyframe = {it.key(), it.value()};
        slotSetPosition(m_currentKeyframe.first);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
801
        emit seekToPos(m_currentKeyframe.first, getKeyframePosition());
802
803
804
        std::pair<double,double> speeds = getSpeed(m_currentKeyframe);
        emit selectedKf(m_currentKeyframe, speeds);
        previousFound = true;
805
    }
806

807
808
809
810
    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
811
        emit seekToPos(m_currentKeyframe.first, getKeyframePosition());
812
813
814
815
816
817
818
819
820
821
822
823
824
825
        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);
826
        it+=2;
827
828
829
        // Update all keyframes after that so that we don't alter the speeds
        while (m_moveNext && it != m_keyframes.end()) {
            m_keyframes.insert(it.key(), it.value() + offset);
830
            it++;
831
        }
832
        updateKeyframes(true);
833
834
835
836
837
838
839
840
841
842
843
        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);
844
        updateKeyframes(true);
845
846
847
848
        update();
    }
}

849
const QString RemapView::getKeyframesData() const
850
851
852
853
854
{
    QStringList result;
    QMapIterator<int, int> i(m_keyframes);
    while (i.hasNext()) {
        i.next();
855
        result << QString("%1=%2").arg(m_service->frames_to_time(i.value(), mlt_time_clock)).arg(GenTime(i.key(), pCore->getCurrentFps()).seconds());
856
857
858
859
    }
    return result.join(QLatin1Char(';'));
}

860
861
void RemapView::reloadProducer()
{
862
863
    if (!m_clip || !m_clip->clipUrl().endsWith(QLatin1String(".mlt"))) {
        qDebug()<<"==== this is not a playlist clip, aborting";
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
889
890
891
892
893
        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);
    }
}

894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
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;
}

914
915
916
917
918
int RemapView::getKeyframePosition() const
{
    QMapIterator<int, int> i(m_keyframes);
    std::pair<int, int> newKeyframe = {-1,-1};
    std::pair<int, int> previous = {-1,-1};
919
    newKeyframe.first = m_position + m_inFrame;
920
921
    while (i.hasNext()) {
        i.next();
922
        if (i.key() > m_position + m_inFrame) {
923
924
            if (i.key() == m_keyframes.firstKey()) {
                // This is the first keyframe
925
926
                double ratio = (double)(m_position + m_inFrame) / i.key();
                return i.value() * ratio - m_inFrame;
927
928
929
                break;
            } else if (previous.first > -1) {
                std::pair<int,int> current = {i.key(), i.value()};
930
931
                double ratio = (double)(m_position + m_inFrame - previous.first) / (current.first - previous.first);
                return previous.second + (qAbs(current.second - previous.second) * ratio) - m_inFrame;
932
933
934
935
936
937
938
939
940
941
                break;
            }
        }
        previous = {i.key(), i.value()};
    }
    if (newKeyframe.second == -1) {
        // We are after the last keyframe
        if (m_keyframes.isEmpty()) {
            return m_position;
        } else {
942
943
            double ratio = (double)(m_position + m_inFrame - 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) - m_inFrame;
944
945
946
947
948
        }
    }
    return m_position;
}

949
950
951
void RemapView::addKeyframe()
{
    // insert or remove keyframe at interpolated position
952
953
954
    if (m_keyframes.contains(m_position + m_inFrame)) {
        m_keyframes.remove(m_position + m_inFrame);
        if (m_currentKeyframe.first == m_position + m_inFrame) {
955
956
957
958
959
            m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
            std::pair<double,double> speeds = {-1,-1};
            emit selectedKf(m_currentKeyframe, speeds);
        }
        emit atKeyframe(false);
960
        updateKeyframes(false);
961
962
963
964
965
966
        update();
        return;
    }
    QMapIterator<int, int> i(m_keyframes);
    std::pair<int, int> newKeyframe = {-1,-1};
    std::pair<int, int> previous = {-1,-1};
967
    newKeyframe.first = m_position + m_inFrame;
968
969
    while (i.hasNext()) {
        i.next();
970
        if (i.key() > m_position + m_inFrame) {
971
972
            if (i.key() == m_keyframes.firstKey()) {
                // This is the first keyframe
973
                double ratio = (double)(m_position + m_inFrame) / i.key();
974
975
976
977
                newKeyframe.second = i.value() * ratio;
                break;
            } else if (previous.first > -1) {
                std::pair<int,int> current = {i.key(), i.value()};
978
                double ratio = (double)(m_position + m_inFrame - previous.first) / (current.first - previous.first);
979
980
981
982
983
984
985
986
987
988
                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()) {
989
            newKeyframe.second = m_position + m_inFrame;
990
        } else {
991
            double ratio = (double)(m_position + m_inFrame - m_keyframes.lastKey()) / (m_duration - m_keyframes.lastKey());
992
993
994
995
996
997
998
999
            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);
1000
    updateKeyframes(true);
1001
1002
1003
    update();
}

1004
1005
1006
1007
void RemapView::toggleMoveNext(bool moveNext)
{
    m_moveNext = moveNext;
}
1008

1009
1010
void RemapView::refreshOnDurationChanged(int remapDuration)
{
1011
1012
    if (remapDuration != m_duration) {
        m_duration = qMax(remapDuration, remapMax());
1013
1014
1015
1016
1017
1018
1019
        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);
    }
}

1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
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();
}

1030
1031
1032
1033
1034
1035
1036
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();
1037
    QColor bg = scheme.background(KColorScheme::AlternateBackground ).color();
1038
    QStylePainter p(this);
1039
1040
    int maxWidth = width() - (2 * m_offset);
    int zoomEnd = qCeil(m_zoomHandle.y() * maxWidth);
1041
    // Top timeline
1042
    p.fillRect(m_offset, 0, maxWidth + 1, m_lineHeight, bg);
1043
    // Bottom timeline
1044
    p.fillRect(m_offset, m_bottomView, maxWidth + 1, m_lineHeight, bg);
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
    /* 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++) {
1085
1086
        scaledTick = int(m_offset + (i * frameSize) + tickOffset);
        if (scaledTick >= maxWidth + m_offset) {
1087
1088
1089
            break;
        }
        p.drawLine(QPointF(scaledTick , m_lineHeight + 1), QPointF(scaledTick, m_lineHeight - 3));
1090
        p.drawLine(QPointF(scaledTick , m_bottomView + 1), QPointF(scaledTick, m_bottomView - 3));
1091
1092
1093
1094
1095
    }

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

    /*
1096
1097
     * Time-"lines"
     * We have a top timeline for the source (clip monitor) and a bottom timeline for the output (project monitor)
1098
1099
     */
    p.setPen(m_colKeyframe);
1100
    // Top timeline
1101
    //qDebug()<<"=== MAX KFR WIDTH: "<<maxWidth<<", DURATION SCALED: "<<(m_duration * m_scale)<<", POS: "<<(m_position * m_scale);
1102
    p.drawLine(m_offset, m_lineHeight, maxWidth + m_offset, m_lineHeight);
1103
    p.drawLine(m_offset, m_lineHeight - m_lineHeight / 4, m_offset, m_lineHeight + m_lineHeight / 4);
1104
    p.drawLine(maxWidth + m_offset, m_lineHeight - m_lineHeight / 4, maxWidth + m_offset, m_lineHeight + m_lineHeight / 4);
1105
    // Bottom timeline
1106
    p.drawLine(m_offset, m_bottomView, maxWidth + m_offset, m_bottomView);
1107
    p.drawLine(m_offset, m_bottomView - m_lineHeight / 4, m_offset, m_bottomView + m_lineHeight / 4);
1108
    p.drawLine(maxWidth + m_offset, m_bottomView - m_lineHeight / 4, maxWidth + m_offset, m_bottomView + m_lineHeight / 4);
1109
    /*
1110
     * Keyframes
1111
     */
1112
1113
1114
    QMapIterator<int, int> i(m_keyframes);
    while (i.hasNext()) {
        i.next();
1115
1116
        double inPos = (double)(i.key() - m_inFrame) * m_scale;
        double outPos = (double)(i.value() - m_inFrame) * m_scale;
1117
        if ((inPos < m_zoomStart && outPos < m_zoomStart) || (qFloor(inPos) > zoomEnd && qFloor(outPos) > zoomEnd)) {
1118
            qDebug()<<"=== KEYFRAME OUTSIDE: "<<inPos<<"x"<<outPos<<", ZOOM ST: "<<m_zoomStart<<"x"<<zoomEnd;
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
            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;
1137

1138
1139
1140
1141
1142
        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));
1143
1144
1145
1146
1147
    }

    /*
     * current position cursor
     */
1148
    p.setPen(m_colSelected);
1149
    if (m_position >= 0 && m_position < m_duration) {
1150
        p.setBrush(m_colSelected);
1151
        double scaledPos = m_position * m_scale;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1152
1153
1154
1155
        scaledPos -= m_zoomStart;
        scaledPos *= m_zoomFactor;
        scaledPos += m_offset;
        if (scaledPos >= m_offset && qFloor(scaledPos) <= m_offset + maxWidth) {
1156
1157
1158
1159
            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);
1160
1161
1162
        }
        int projectPos = getKeyframePosition();
        double scaledPos2 = projectPos * m_scale;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1163
1164
1165
1166
        scaledPos2 -= m_zoomStart;
        scaledPos2 *= m_zoomFactor;
        scaledPos2 += m_offset;
        if (scaledPos2 >= m_offset && qFloor(scaledPos2) <= m_offset + maxWidth) {
1167
1168
1169
            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);
1170
            p.setBrush(m_colSelected);
1171
            p.drawPolygon(bottomCursor  );
1172
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1173
1174
1175
        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);
1176
    }
1177
1178

    // Zoom bar
1179
    p.setPen(Qt::NoPen);
1180
    p.setBrush(palette().mid());
1181
    p.drawRoundedRect(0, m_bottomView + m_lineHeight + 4, width() - 2 * 0, m_zoomHeight, m_lineHeight / 3, m_lineHeight / 3);
1182
1183
    p.setBrush(palette().highlight());
    p.drawRoundedRect(int((width()) * m_zoomHandle.x()),
1184
                      m_bottomView + m_lineHeight + 4,
1185
                      int((width()) * (m_zoomHandle.y() - m_zoomHandle.x())),
1186
1187
                      m_zoomHeight,
                      m_lineHeight / 3, m_lineHeight / 3);
1188
1189
1190
1191
1192
}


TimeRemap::TimeRemap(QWidget *parent)
    : QWidget(parent)
1193
    , m_cid(-1)
1194
1195
1196
1197
1198
1199
1200
1201
1202
{
    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);
1203
1204
    speedBefore->setKeyboardTracking(false);
    speedAfter->setKeyboardTracking(false);
1205
    remapLayout->addWidget(m_view);
1206
    connect(m_view, &RemapView::selectedKf, [this](std::pair<int,int>selection, std::pair<double,double>speeds) {
1207
        info_frame->setEnabled(selection.first >= 0);
1208
1209
1210
1211
        QSignalBlocker bk(m_in);
        QSignalBlocker bk2(m_out);
        m_in->setEnabled(selection.first >= 0);
        m_out->setEnabled(selection.first >= 0);
1212
1213
        m_in->setValue(selection.first - m_view->m_inFrame);
        m_out->setValue(selection.second - m_view->m_inFrame);
1214
1215
1216
1217
1218
1219
1220
1221
1222
        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]() {
1223
        m_view->updateInPos(m_in->getValue() + m_view->m_inFrame);
1224
    });
1225
    button_center->setToolTip(i18n("Move selected keyframe to cursor"));
1226
    connect(m_out, &TimecodeDisplay::timeCodeUpdated, [this]() {
1227
        m_view->updateOutPos(m_out->getValue() + m_view->m_inFrame);
1228
    });
1229
    connect(button_center, &QToolButton::clicked, m_view, &RemapView::centerCurrentKeyframe);
1230
1231
1232
1233
1234
1235
1236
1237
1238
    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);
    });
1239
1240
1241
1242
1243
1244
1245
    connect(button_del, &QToolButton::clicked, [this]() {
        if (m_cid > -1) {
            std::shared_ptr<TimelineItemModel> model = pCore->window()->getCurrentTimeline()->controller()->getModel();
            model->requestClipTimeRemap(m_cid, false);
            selectedClip(-1);
        }
    });
1246
1247
1248
1249
    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);
1250
    connect(pitch_compensate, &QCheckBox::toggled, this, &TimeRemap::switchPitch);
1251
    connect(frame_blending, &QCheckBox::toggled, this, &TimeRemap::switchBlending);
1252
    connect(m_view, &RemapView::updateMaxDuration, [this](int duration) {
1253
1254
1255
        int min = m_in->minimum();
        m_out->setRange(0, INT_MAX);
        m_in->setRange(0, duration - 1);
1256
    });
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1257
    setEnabled(false);
1258
1259
}

1260
1261
1262
1263
1264
const QString &TimeRemap::currentClip() const
{
    return m_binId;
}

1265
1266
1267
1268
1269
1270