keyframeview.cpp 33.7 KB
Newer Older
1
2
/***************************************************************************
 *   Copyright (C) 2011 by Till Theato (root@ttill.de)                     *
3
 *   Copyright (C) 2017 by Nicolas Carion                                  *
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 *   This file is part of Kdenlive (www.kdenlive.org).                     *
 *                                                                         *
 *   Kdenlive 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.                                   *
 *                                                                         *
 *   Kdenlive 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 Kdenlive.  If not, see <http://www.gnu.org/licenses/>.     *
 ***************************************************************************/

#include "keyframeview.hpp"
21
#include "assets/keyframes/model/keyframemodellist.hpp"
22
23
24
25
#include "core.h"
#include "kdenlivesettings.h"

#include <QMouseEvent>
26
#include <QApplication>
27
#include <QStylePainter>
28
#include <QtMath>
29
30
31

#include <KColorScheme>
#include <QFontDatabase>
Nicolas Carion's avatar
Nicolas Carion committed
32
#include <utility>
33

34
KeyframeView::KeyframeView(std::shared_ptr<KeyframeModelList> model, int duration, int inPoint, QWidget *parent)
35
    : QWidget(parent)
Nicolas Carion's avatar
Nicolas Carion committed
36
    , m_model(std::move(model))
37
    , m_duration(duration)
38
    , m_inPoint(inPoint)
39
40
41
42
43
    , m_position(0)
    , m_currentKeyframe(-1)
    , m_currentKeyframeOriginal(-1)
    , m_hoverKeyframe(-1)
    , m_scale(1)
44
45
    , m_zoomFactor(1)
    , m_zoomStart(0)
46
    , m_moveKeyframeMode(false)
47
48
    , m_clickPoint(-1)
    , m_clickEnd(-1)
49
50
51
52
53
    , m_zoomHandle(0,1)
    , m_hoverZoomIn(false)
    , m_hoverZoomOut(false)
    , m_hoverZoom(false)
    , m_clickOffset(0)
54
55
56
57
58
{
    setMouseTracking(true);
    setMinimumSize(QSize(150, 20));
    setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
    QPalette p = palette();
59
    KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window);
60
61
    m_colSelected = palette().highlight().color();
    m_colKeyframe = scheme.foreground(KColorScheme::NormalText).color();
62
    m_size = QFontInfo(font()).pixelSize() * 3;
Vincent Pinon's avatar
Vincent Pinon committed
63
    m_lineHeight = int(m_size / 2.1);
64
65
    m_zoomHeight = m_size * 3 / 4;
    m_offset = m_size / 4;
66
    setFixedHeight(m_size);
67
    setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
68
    connect(m_model.get(), &KeyframeModelList::modelChanged, this, &KeyframeView::slotModelChanged);
69
70
}

71
72
void KeyframeView::slotModelChanged()
{
73
74
    int offset = pCore->getItemIn(m_model->getOwnerId());
    emit atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
75
    emit modified();
76
77
    update();
}
78

79
void KeyframeView::slotSetPosition(int pos, bool isInRange)
80
{
81
82
83
84
85
    if (!isInRange) {
        m_position = -1;
        update();
        return;
    }
86
87
    if (pos != m_position) {
        m_position = pos;
88
89
        int offset = pCore->getItemIn(m_model->getOwnerId());
        emit atKeyframe(m_model->hasKeyframe(pos + offset), m_model->singleKeyframe());
90
        double zoomPos = double(m_position) / m_duration;
91
92
93
94
95
96
97
98
99
100
101
        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);
        }
102
103
104
105
        update();
    }
}

106
107
108
109
110
void KeyframeView::initKeyframePos()
{
    emit atKeyframe(m_model->hasKeyframe(m_position), m_model->singleKeyframe());
}

111
112
113
114
115
116
117
void KeyframeView::slotDuplicateKeyframe()
{
    int offset = pCore->getItemIn(m_model->getOwnerId());
    if (m_currentKeyframe > -1 && !m_model->hasKeyframe(m_position + offset)) {
        Fun undo = []() { return true; };
        Fun redo = []() { return true; };
        int delta = m_position - m_currentKeyframe;
118
        for (int kf : qAsConst(m_selectedKeyframes)) {
119
120
121
122
123
124
            m_model->duplicateKeyframeWithUndo(GenTime(kf + offset, pCore->getCurrentFps()), GenTime(kf + delta + offset, pCore->getCurrentFps()), undo, redo);
        }
        pCore->pushUndo(undo, redo, i18n("Duplicate keyframe"));
    }
}

125
void KeyframeView::slotAddKeyframe(int pos)
126
127
128
129
{
    if (pos < 0) {
        pos = m_position;
    }
130
    int offset = pCore->getItemIn(m_model->getOwnerId());
131
    m_model->addKeyframe(GenTime(pos + offset, pCore->getCurrentFps()), KeyframeType(KdenliveSettings::defaultkeyframeinterp()));
132
133
}

134
135
136
137
138
const QString KeyframeView::getAssetId()
{
    return m_model->getAssetId();
}

139
void KeyframeView::slotAddRemove()
140
{
141
    emit activateEffect();
142
143
    int offset = pCore->getItemIn(m_model->getOwnerId());
    if (m_model->hasKeyframe(m_position + offset)) {
144
145
146
147
148
149
        if (m_selectedKeyframes.contains(m_position)) {
            // Delete all selected keyframes
            slotRemoveKeyframe(m_selectedKeyframes);
        } else {
            slotRemoveKeyframe({m_position});
        }
150
    } else {
151
        slotAddKeyframe(m_position);
152
153
154
    }
}

155
156
void KeyframeView::slotEditType(int type, const QPersistentModelIndex &index)
{
157
158
    int offset = pCore->getItemIn(m_model->getOwnerId());
    if (m_model->hasKeyframe(m_position + offset)) {
159
        m_model->updateKeyframeType(GenTime(m_position + offset, pCore->getCurrentFps()), type, index);
160
161
162
    }
}

163
void KeyframeView::slotRemoveKeyframe(QVector<int> positions)
164
{
165
166
    if (m_model->singleKeyframe()) {
        // Don't allow zero keyframe
167
        pCore->displayMessage(i18n("Cannot remove the last keyframe"), MessageType::ErrorMessage, 500);
168
169
        return;
    }
170
    int offset = pCore->getItemIn(m_model->getOwnerId());
171
172
173
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
    for (int pos : positions) {
174
175
176
177
        if (pos == 0) {
            // Don't allow moving first keyframe
            continue;
        }
178
179
180
181
182
183
        if (m_selectedKeyframes.contains(pos)) {
            m_selectedKeyframes.removeAll(pos);
        }
        m_model->removeKeyframeWithUndo(GenTime(pos + offset, pCore->getCurrentFps()), undo, redo);
    }
    pCore->pushUndo(undo, redo, i18np("Remove keyframe", "Remove keyframes", positions.size()));
184
185
}

186
void KeyframeView::setDuration(int dur, int inPoint)
187
188
{
    m_duration = dur;
189
    m_inPoint = inPoint;
190
191
    int offset = pCore->getItemIn(m_model->getOwnerId());
    emit atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
192
193
194
195
196
197
198
199
200
201
    // Unselect keyframes that are outside range if any
    QVector<int> toDelete;
    for (auto &p : m_selectedKeyframes) {
        if (p < m_inPoint || p >= m_inPoint + m_duration) {
            toDelete << p;
        }
    }
    for (auto &p : toDelete) {
        m_selectedKeyframes.removeAll(p);
    }
202
    update();
203
204
205
206
}

void KeyframeView::slotGoToNext()
{
207
    emit activateEffect();
208
    if (m_position == m_duration - 1) {
209
210
211
212
        return;
    }

    bool ok;
213
    int offset = pCore->getItemIn(m_model->getOwnerId());
214
    auto next = m_model->getNextKeyframe(GenTime(m_position + offset, pCore->getCurrentFps()), &ok);
215
216

    if (ok) {
217
        emit seekToPos(qMin(int(next.first.frames(pCore->getCurrentFps())) - offset, m_duration - 1) + m_inPoint);
218
219
    } else {
        // no keyframe after current position
220
        emit seekToPos(m_duration - 1 + m_inPoint);
221
222
223
224
225
    }
}

void KeyframeView::slotGoToPrev()
{
226
    emit activateEffect();
227
228
229
230
231
    if (m_position == 0) {
        return;
    }

    bool ok;
232
233
    int offset = pCore->getItemIn(m_model->getOwnerId());
    auto prev = m_model->getPrevKeyframe(GenTime(m_position + offset, pCore->getCurrentFps()), &ok);
234
235

    if (ok) {
236
        emit seekToPos(qMax(0, int(prev.first.frames(pCore->getCurrentFps())) - offset) + m_inPoint);
237
238
    } else {
        // no keyframe after current position
239
        emit seekToPos(m_duration - 1 + m_inPoint);
240
241
242
    }
}

243
244
void KeyframeView::slotCenterKeyframe()
{
245
    if (m_currentKeyframeOriginal == -1 || m_currentKeyframeOriginal == m_position || m_currentKeyframeOriginal == 0) {
246
247
248
249
250
251
        return;
    }
    int offset = pCore->getItemIn(m_model->getOwnerId());
    if (!m_model->hasKeyframe(m_currentKeyframeOriginal + offset)) {
        return;
    }
252
253
254
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
    int delta = m_position - m_currentKeyframeOriginal;
255
256
257
    QVector<int>currentSelection = m_selectedKeyframes;
    int sourcePosition = m_currentKeyframeOriginal;
    QVector<int>updatedSelection;
258
    for (int kf : qAsConst(m_selectedKeyframes)) {
259
        if (kf == 0) {
260
            // Don't allow moving first keyframe
261
262
            continue;
        }
263
264
265
        GenTime initPos(kf + offset, pCore->getCurrentFps());
        GenTime targetPos(kf + delta + offset, pCore->getCurrentFps());
        m_model->moveKeyframeWithUndo(initPos, targetPos, undo, redo);
266
        updatedSelection << (kf + delta);
267
    }
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
    Fun local_redo = [this, updatedSelection]() {
        m_currentKeyframe = m_currentKeyframeOriginal = m_position;
        m_selectedKeyframes = updatedSelection;
        update();
        return true;
    };
    Fun local_undo = [this, currentSelection, sourcePosition]() {
        m_currentKeyframe = m_currentKeyframeOriginal = sourcePosition;
        m_selectedKeyframes = currentSelection;
        update();
        return true;
    };
    local_redo();
    PUSH_LAMBDA(local_redo, redo);
    PUSH_FRONT_LAMBDA(local_undo, undo);
283
    pCore->pushUndo(undo, redo, i18nc("@action", "Move keyframe"));
284
285
}

286
287
void KeyframeView::mousePressEvent(QMouseEvent *event)
{
288
    emit activateEffect();
289
    int offset = pCore->getItemIn(m_model->getOwnerId());
290
291
292
    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);
Vincent Pinon's avatar
Vincent Pinon committed
293
    int pos = int(((event->x() - m_offset) / zoomFactor + zoomStart ) / m_scale);
294
    pos = qBound(0, pos, m_duration - 1);
295
    m_moveKeyframeMode = false;
296
    if (event->button() == Qt::LeftButton) {
297
        if (event->y() < m_lineHeight) {
298
299
300
            // mouse click in keyframes area
            bool ok;
            GenTime position(pos + offset, pCore->getCurrentFps());
301
302
303
304
            if (event->modifiers() & Qt::ShiftModifier) {
                m_clickPoint = pos;
                return;
            }
305
            auto keyframe = m_model->getClosestKeyframe(position, &ok);
306
            if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
307
                m_currentKeyframeOriginal = keyframe.first.frames(pCore->getCurrentFps()) - offset;
308
309
310
                if (event->modifiers() & Qt::ControlModifier) {
                    if (m_selectedKeyframes.contains(m_currentKeyframeOriginal)) {
                        m_selectedKeyframes.removeAll(m_currentKeyframeOriginal);
311
                        m_currentKeyframeOriginal = -1;
312
                    } else {
313
314
315
316
317
                        m_selectedKeyframes << m_currentKeyframeOriginal;
                    }
                } else if (!m_selectedKeyframes.contains(m_currentKeyframeOriginal)) {
                    m_selectedKeyframes = {m_currentKeyframeOriginal};
                }
318
319
                // Select and seek to keyframe
                m_currentKeyframe = m_currentKeyframeOriginal;
320
321
322
                if (m_currentKeyframeOriginal > -1) {
                    m_moveKeyframeMode = true;
                    if (KdenliveSettings::keyframeseek()) {
323
                        emit seekToPos(m_currentKeyframeOriginal + m_inPoint);
324
325
326
                    } else {
                        update();
                    }
327
328
329
                } else {
                    update();
                }
330
331
                return;
            }
332
            // no keyframe next to mouse
333
            m_selectedKeyframes.clear();
334
            m_currentKeyframe = m_currentKeyframeOriginal = -1;
335
336
337
        } else if (event->y() > m_zoomHeight + 2) {
            // click on zoom area
            if (m_hoverZoom) {
338
                m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
            }
            return;
        }
    } else if (event->button() == Qt::RightButton && event->y() > m_zoomHeight + 2) {
        // 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();
354
            return;
355
356
        }
    }
357
358
359
360
    if (pos != m_position) {
        emit seekToPos(pos + m_inPoint);
        update();
    }
361
362
363
364
}

void KeyframeView::mouseMoveEvent(QMouseEvent *event)
{
365
    int offset = pCore->getItemIn(m_model->getOwnerId());
366
367
368
    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);
369
    int pos = int(((double(event->x()) - m_offset) / zoomFactor + zoomStart ) / m_scale);
370
    pos = qBound(0, pos, m_duration - 1);
371
    GenTime position(pos + offset, pCore->getCurrentFps());
372
    if ((event->buttons() & Qt::LeftButton) != 0u) {
373
374
375
        if (m_hoverZoomIn || m_hoverZoomOut || m_hoverZoom) {
            // Moving zoom handles
            if (m_hoverZoomIn) {
376
                m_zoomHandle.setX(qMin(qMax(0., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.y() - 0.015));
377
378
379
380
                update();
                return;
            }
            if (m_hoverZoomOut) {
381
                m_zoomHandle.setY(qMax(qMin(1., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.x() + 0.015));
382
383
384
385
386
                update();
                return;
            }
            // moving zoom zone
            if (m_hoverZoom) {
387
                double clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset) - m_clickOffset;
388
389
390
391
392
393
394
395
396
397
398
                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;
                }
399
                m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
400
401
402
403
404
                m_zoomHandle = QPointF(newX, newY);
                update();
            }
            return;
        }
405
406
407
        if (m_currentKeyframe == pos) {
            return;
        }
408
        if (m_currentKeyframe > 0 && m_moveKeyframeMode) {
409
            if (!m_model->hasKeyframe(pos + offset)) {
410
                int delta = pos - m_currentKeyframe;
411
                // Check that the move is possible
412
                for (int kf : qAsConst(m_selectedKeyframes)) {
413
414
415
                    int updatedPos = kf + offset + delta;
                    if (!m_selectedKeyframes.contains(updatedPos) && m_model->hasKeyframe(updatedPos)) {
                        // Don't allow moving over another keyframe
416
417
418
                        return;
                    }
                }
419
420
421
422
423
424
                if (delta > 0) {
                    // Sort kfrs in revert order to prevent conflicts
                    std::sort(m_selectedKeyframes.rbegin(), m_selectedKeyframes.rend());
                } else {
                    std::sort(m_selectedKeyframes.begin(), m_selectedKeyframes.end());
                }
425
                for (int kf : qAsConst(m_selectedKeyframes)) {
426
427
428
429
                    if (kf == 0) {
                        // Don't allow moving first keyframe
                        continue;
                    }
430
431
432
433
434
435
                    GenTime currentPos(kf + offset, pCore->getCurrentFps());
                    GenTime updatedPos(kf + offset + delta, pCore->getCurrentFps());
                    if (m_model->moveKeyframe(currentPos, updatedPos, false)) {
                        if (kf == m_currentKeyframe) {
                           m_currentKeyframe = pos;
                        }
436
437
438
                    } else {
                        qDebug()<<"=== FAILED KF MOVE!!!";
                        Q_ASSERT(false);
439
440
441
                    }
                }
                for (int &kf : m_selectedKeyframes) {
442
443
444
445
                    if (kf == 0) {
                        // Don't allow moving first keyframe
                        continue;
                    }
446
                    kf += delta;
447
448
449
                }
            }
        }
450
451
452
453
454
455
456
        // 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();
457
            m_currentKeyframeOriginal = m_currentKeyframe = -1;
458
459
460
            double fps = pCore->getCurrentFps();
            for (const auto &keyframe : *m_model.get()) {
                int pos = keyframe.first.frames(fps) - offset;
461
                if (pos > min && pos <= max) {
462
463
464
                    m_selectedKeyframes << pos;
                }
            }
465
466
467
            if (!m_selectedKeyframes.isEmpty()) {
                m_currentKeyframeOriginal = m_currentKeyframe = m_selectedKeyframes.first();
            }
468
469
470
471
            update();
            return;
        }
        
472
        if (!m_moveKeyframeMode || KdenliveSettings::keyframeseek()) {
473
474
475
            if (pos != m_position) {
                emit seekToPos(pos + m_inPoint);
            }
476
        }
477
478
479
480
481
        return;
    }
    if (event->y() < m_lineHeight) {
        bool ok;
        auto keyframe = m_model->getClosestKeyframe(position, &ok);
482
        if (ok && qAbs(((position.frames(pCore->getCurrentFps()) - keyframe.first.frames(pCore->getCurrentFps())) * m_scale) * m_zoomFactor) < QApplication::startDragDistance()) {
483
            m_hoverKeyframe = keyframe.first.frames(pCore->getCurrentFps()) - offset;
484
            setCursor(Qt::PointingHandCursor);
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
513
514
            m_hoverZoomIn = false;
            m_hoverZoomOut = false;
            m_hoverZoom = false;
            update();
            return;
        }
    }
    if (event->y() > m_zoomHeight + 2) {
        // 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;
515
516
517
518
519
            update();
            return;
        }
    }

520
    if (m_hoverKeyframe != -1 || m_hoverZoomOut || m_hoverZoomIn || m_hoverZoom) {
521
        m_hoverKeyframe = -1;
522
523
524
        m_hoverZoomOut = false;
        m_hoverZoomIn = false;
        m_hoverZoom = false;
525
526
527
528
529
530
531
532
        setCursor(Qt::ArrowCursor);
        update();
    }
}

void KeyframeView::mouseReleaseEvent(QMouseEvent *event)
{
    Q_UNUSED(event)
533
    m_moveKeyframeMode = false;
534
535
536
537
538
    m_clickPoint = -1;
    if (m_clickEnd >= 0) {
        m_clickEnd = -1;
        update();
    }
539
    if (m_currentKeyframe >= 0 && m_currentKeyframeOriginal != m_currentKeyframe) {
540
        int offset = pCore->getItemIn(m_model->getOwnerId());
541
542
        int delta = m_currentKeyframe - m_currentKeyframeOriginal;
        // Move back all keyframes to their initial positions
543
544
545
546
547
548
        // Sort keyframes so we don't move a keyframe over another one
        if (delta > 0) {
            std::sort(m_selectedKeyframes.begin(), m_selectedKeyframes.end());
        } else {
            std::sort(m_selectedKeyframes.begin(), m_selectedKeyframes.end(), std::greater<>());
        }
549
        for (int kf : qAsConst(m_selectedKeyframes)) {
550
551
552
553
            if (kf == 0) {
                // Don't allow moving first keyframe
                continue;
            }
554
555
556
557
558
559
560
            GenTime initPos(kf - delta + offset, pCore->getCurrentFps());
            GenTime targetPos(kf + offset, pCore->getCurrentFps());
            m_model->moveKeyframe(targetPos, initPos, false);
        }
        // Move all keyframes to their new positions
        Fun undo = []() { return true; };
        Fun redo = []() { return true; };
561
562
563
564
565
566
        // Sort keyframes so we don't move a keyframe over another one
        if (delta > 0) {
            std::sort(m_selectedKeyframes.begin(), m_selectedKeyframes.end(), std::greater<>());
        } else {
            std::sort(m_selectedKeyframes.begin(), m_selectedKeyframes.end());
        }
567
        for (int kf : qAsConst(m_selectedKeyframes)) {
568
569
570
571
            if (kf == 0) {
                // Don't allow moving first keyframe
                continue;
            }
572
573
574
575
            GenTime initPos(kf - delta + offset, pCore->getCurrentFps());
            GenTime targetPos(kf + offset, pCore->getCurrentFps());
            m_model->moveKeyframeWithUndo(initPos, targetPos, undo, redo);
        }
576
        m_currentKeyframeOriginal = m_currentKeyframe;
577
578
        pCore->pushUndo(undo, redo, i18np("Move keyframe", "Move keyframes", m_selectedKeyframes.size()));
        qDebug() << "RELEASING keyframe move" << delta;
579
580
581
582
583
584
    }
}

void KeyframeView::mouseDoubleClickEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton && event->y() < m_lineHeight) {
585
        int offset = pCore->getItemIn(m_model->getOwnerId());
586
587
588
        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);
Vincent Pinon's avatar
Vincent Pinon committed
589
        int pos = int(((event->x() - m_offset) / zoomFactor + zoomStart ) / m_scale);
590
        pos = qBound(0, pos, m_duration - 1);
591
        GenTime position(pos + offset, pCore->getCurrentFps());
592
593
        bool ok;
        auto keyframe = m_model->getClosestKeyframe(position, &ok);
594
        if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset)* m_scale * m_zoomFactor < QApplication::startDragDistance()) {
595
596
597
            if (keyframe.first.frames(pCore->getCurrentFps()) != offset) {
                m_model->removeKeyframe(keyframe.first);
                if (keyframe.first.frames(pCore->getCurrentFps()) == m_currentKeyframe + offset) {
598
599
600
                    if (m_selectedKeyframes.contains(m_currentKeyframe)) {
                        m_selectedKeyframes.removeAll(m_currentKeyframe);
                    }
601
602
603
604
605
                    m_currentKeyframe = m_currentKeyframeOriginal = -1;
                }
                if (keyframe.first.frames(pCore->getCurrentFps()) == m_position + offset) {
                    emit atKeyframe(false, m_model->singleKeyframe());
                }
606
607
608
609
610
            }
            return;
        }

        // add new keyframe
611
        m_model->addKeyframe(position, KeyframeType(KdenliveSettings::defaultkeyframeinterp()));
612
613
614
615
616
617
618
    } else {
        QWidget::mouseDoubleClickEvent(event);
    }
}

void KeyframeView::wheelEvent(QWheelEvent *event)
{
619
    if (event->modifiers() & Qt::AltModifier) {
620
621
        // Alt modifier seems to invert x/y axis
        if (event->angleDelta().x() > 0) {
622
623
624
625
626
627
            slotGoToPrev();
        } else {
            slotGoToNext();
        }
        return;
    }
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
    if (event->modifiers() & Qt::ControlModifier) {
        int maxWidth = width() - 2 * m_offset;
        m_zoomStart = m_zoomHandle.x() * maxWidth;
        m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
        double scaledPos = m_position * m_scale;
        double zoomRange = (m_zoomHandle.y() - m_zoomHandle.x()) * maxWidth;
        if (event->angleDelta().y() > 0) {
            zoomRange /= 1.5;
        } else {
            zoomRange *= 1.5;
        }
        if (zoomRange < 5) {
            // Don't allow too small zoombar
            return;
        }
        double length = (scaledPos - zoomRange / 2) / maxWidth;
        m_zoomHandle.setX(qMax(0., length));
        if (length < 0) {
            m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth - length));
        } else {
            m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth));
        }
        update();
        return;
    }
Laurent Montel's avatar
Laurent Montel committed
653
    int change = event->angleDelta().y() > 0 ? -1 : 1;
654
    int pos = qBound(0, m_position + change, m_duration - 1);
655
    emit seekToPos(pos + m_inPoint);
656
657
658
659
660
661
662
}

void KeyframeView::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)

    QStylePainter p(this);
663
    int maxWidth = width() - 2 * m_offset;
664
665
666
667
668
    if (m_duration > 1) {
        m_scale = maxWidth / double(m_duration - 1);
    } else {
        m_scale = maxWidth;
    }
669
    int headOffset = m_lineHeight / 2;
670
    int offset = pCore->getItemIn(m_model->getOwnerId());
671
    m_zoomStart = m_zoomHandle.x() * maxWidth;
672
673
    m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
    int zoomEnd = qCeil(m_zoomHandle.y() * maxWidth);
674
675
    /* ticks */
    double fps = pCore->getCurrentFps();
676
    int displayedLength = int(m_duration / m_zoomFactor / fps);
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
    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;
Vincent Pinon's avatar
Vincent Pinon committed
709
    int base = int(tickOffset / frameSize);
710
711
712
713
    tickOffset = frameSize - (tickOffset - (base * frameSize));
    // Draw frame ticks
    int scaledTick = 0;
    for (int i = 0; i < maxWidth / frameSize; i++) {
Vincent Pinon's avatar
Vincent Pinon committed
714
        scaledTick = int(m_offset + (i * frameSize) + tickOffset);
715
716
717
718
719
720
        if (scaledTick >= maxWidth + m_offset) {
            break;
        }
        p.drawLine(QPointF(scaledTick , m_lineHeight + 1), QPointF(scaledTick, m_lineHeight - 3));
    }

721
722
723
724
725

    /*
     * keyframes
     */
    for (const auto &keyframe : *m_model.get()) {
726
        int pos = keyframe.first.frames(fps) - offset;
727
        if (pos < 0) continue;
728
729
730
731
        double scaledPos = pos * m_scale;
        if (scaledPos < m_zoomStart || qFloor(scaledPos) > zoomEnd) {
            continue;
        }
732
733
734
        if (pos == m_currentKeyframe) {
            p.setBrush(Qt::red);
        } else if (m_selectedKeyframes.contains(pos)) {
735
            p.setBrush(Qt::darkRed);
736
        } else if (pos == m_currentKeyframe || pos == m_hoverKeyframe) {
737
            p.setBrush(m_colSelected);
738
739
740
        } else {
            p.setBrush(m_colKeyframe);
        }
741
742
        scaledPos -= m_zoomStart;
        scaledPos *= m_zoomFactor;
743
744
        scaledPos += m_offset;
        p.drawLine(QPointF(scaledPos, headOffset), QPointF(scaledPos, m_lineHeight - 1));
745
        switch (keyframe.second.first) {
Nicolas Carion's avatar
Nicolas Carion committed
746
747
748
749
750
751
752
753
754
755
756
757
758
        case KeyframeType::Linear: {
            QPolygonF position = QPolygonF() << QPointF(-headOffset / 2.0, headOffset / 2.0) << QPointF(0, 0) << QPointF(headOffset / 2.0, headOffset / 2.0)
                                             << QPointF(0, headOffset);
            position.translate(scaledPos, 0);
            p.drawPolygon(position);
            break;
        }
        case KeyframeType::Discrete:
            p.drawRect(QRectF(scaledPos - headOffset / 2.0, 0, headOffset, headOffset));
            break;
        default:
            p.drawEllipse(QRectF(scaledPos - headOffset / 2.0, 0, headOffset, headOffset));
            break;
759
        }
760
761
762
    }

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

764
765
766
767
    /*
     * Time-"line"
     */
    p.setPen(m_colKeyframe);
768
    p.drawLine(m_offset, m_lineHeight, width() - m_offset, m_lineHeight);
769
770
    p.drawLine(m_offset, m_lineHeight - headOffset / 2, m_offset, m_lineHeight + headOffset / 2);
    p.drawLine(width() - m_offset, m_lineHeight - headOffset / 2, width() - m_offset, m_lineHeight + headOffset / 2);
771
772

    /*
773
     * current position cursor
774
     */
775
    if (m_position >= 0 && m_position < m_duration) {
776
        double scaledPos = m_position * m_scale;
777
        if (scaledPos >= m_zoomStart && qFloor(scaledPos) <= zoomEnd) {
778
779
            scaledPos -= m_zoomStart;
            scaledPos *= m_zoomFactor;
780
781
            scaledPos += m_offset;
            QPolygon pa(3);
Vincent Pinon's avatar
Vincent Pinon committed
782
            int cursorwidth = int((m_zoomHeight - m_lineHeight) / 1.8);
783
            QPolygonF position = QPolygonF() << QPointF(-cursorwidth, m_zoomHeight - 3) << QPointF(cursorwidth, m_zoomHeight - 3) << QPointF(0, m_lineHeight + 1);
784
785
786
787
788
            position.translate(scaledPos, 0);
            p.setBrush(m_colKeyframe);
            p.drawPolygon(position);
        }
    }
789
790
    // Rubberband
    if (m_clickEnd >= 0) {
Vincent Pinon's avatar
Vincent Pinon committed
791
792
        int min = int((qMin(m_clickPoint, m_clickEnd) * m_scale - m_zoomStart) * m_zoomFactor + m_offset);
        int max = int((qMax(m_clickPoint, m_clickEnd) * m_scale - m_zoomStart) * m_zoomFactor + m_offset);
793
794
795
796
        p.setOpacity(0.5);
        p.fillRect(QRect(min, 0, max - min, m_lineHeight), palette().highlight());
        p.setOpacity(1);
    }
797

798
799
800
801
802
    // Zoom bar
    p.setPen(Qt::NoPen);
    p.setBrush(palette().mid());
    p.drawRoundedRect(m_offset, m_zoomHeight + 3, width() - 2 * m_offset, m_size - m_zoomHeight - 3, m_lineHeight / 5, m_lineHeight / 5);
    p.setBrush(palette().highlight());
Vincent Pinon's avatar
Vincent Pinon committed
803
804
805
806
807
    p.drawRoundedRect(int(m_offset + (width() - m_offset) * m_zoomHandle.x()),
                      m_zoomHeight + 3,
                      int((width() - 2 * m_offset) * (m_zoomHandle.y() - m_zoomHandle.x())),
                      m_size - m_zoomHeight - 3,
                      m_lineHeight / 5, m_lineHeight / 5);
808
}
809
810
811
812


void KeyframeView::copyCurrentValue(QModelIndex ix, const  QString paramName)
{
813
814
    int offset = pCore->getItemIn(m_model->getOwnerId());
    const QString val = m_model->getInterpolatedValue(m_position + offset, ix).toString();
815
816
    QString newVal;
    const QStringList vals = val.split(QLatin1Char(' '));
817
    qDebug()<<"=== COPYING VALS: "<<val<<" AT POS: "<<m_position<<", PARAM NAME_ "<<paramName;
818
    auto *parentCommand = new QUndoCommand();
819
    bool multiParams = paramName.contains(QLatin1Char(' '));
820
    for (int kf : qAsConst(m_selectedKeyframes)) {
821
822
        QString oldValue = m_model->getInterpolatedValue(kf, ix).toString();
        QStringList oldVals = oldValue.split(QLatin1Char(' '));
823
824
        bool found = false;
        if (paramName.contains(QLatin1String("spinX"))) {
825
826
            oldVals[0] = vals.at(0);
            newVal = oldVals.join(QLatin1Char(' '));
827
828
829
830
831
832
            found = true;
            if (!multiParams) {
                parentCommand->setText(i18n("Update keyframes X position"));
            }
        }
        if (paramName.contains(QLatin1String("spinY"))) {
833
834
            oldVals[1] = vals.at(1);
            newVal = oldVals.join(QLatin1Char(' '));
835
836
837
838
839
840
            found = true;
            if (!multiParams) {
                parentCommand->setText(i18n("Update keyframes Y position"));
            }
        }
        if (paramName.contains(QLatin1String("spinW"))) {
841
842
            oldVals[2] = vals.at(2);
            newVal = oldVals.join(QLatin1Char(' '));
843
844
845
846
847
848
            found = true;
            if (!multiParams) {
                parentCommand->setText(i18n("Update keyframes width"));
            }
        }
        if (paramName.contains(QLatin1String("spinH"))) {
849
850
            oldVals[3] = vals.at(3);
            newVal = oldVals.join(QLatin1Char(' '));
851
852
853
854
855
856
            found = true;
            if (!multiParams) {
                parentCommand->setText(i18n("Update keyframes height"));
            }
        }
        if (paramName.contains(QLatin1String("spinO"))) {
857
858
            oldVals[4] = vals.at(4);
            newVal = oldVals.join(QLatin1Char(' '));
859
860
861
862
863
864
            found = true;
            if (!multiParams) {
                parentCommand->setText(i18n("Update keyframes opacity"));
            }
        }
        if (!found) {
865
866
            newVal = val;
            parentCommand->setText(i18n("Update keyframes value"));
867
868
        } else if (multiParams) {
            parentCommand->setText(i18n("Update keyframes value"));
869
        }
870
871
872
873
        bool result = m_model->updateKeyframe(GenTime(kf + offset, pCore->getCurrentFps()), newVal, ix, parentCommand);
        if (result) {
            pCore->displayMessage(i18n("Keyframe value copied"), InformationMessage);
        }
874
875
876
    }
    pCore->pushUndo(parentCommand);
}