keyframeview.cpp 11.8 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
26
27
28
29
30
#include "core.h"
#include "kdenlivesettings.h"

#include <QMouseEvent>
#include <QStylePainter>

#include <KColorScheme>
#include <QFontDatabase>

31
KeyframeView::KeyframeView(std::shared_ptr<KeyframeModelList> model, int duration, QWidget *parent)
32
33
    : QWidget(parent)
    , m_model(model)
34
    , m_duration(duration)
35
36
37
38
39
40
41
42
43
44
45
46
    , m_position(0)
    , m_currentKeyframe(-1)
    , m_currentKeyframeOriginal(-1)
    , m_hoverKeyframe(-1)
    , m_scale(1)
    , m_currentType(KeyframeType::Linear)
{
    setMouseTracking(true);
    setMinimumSize(QSize(150, 20));
    setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum));
    setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
    QPalette p = palette();
47
    KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window);
48
49
50
51
52
53
    m_colSelected = palette().highlight().color();
    m_colKeyframe = scheme.foreground(KColorScheme::NormalText).color();
    m_size = QFontInfo(font()).pixelSize() * 1.8;
    m_lineHeight = m_size / 2;
    setMinimumHeight(m_size);
    setMaximumHeight(m_size);
54
    connect(m_model.get(), &KeyframeModelList::modelChanged, this, &KeyframeView::slotModelChanged);
55
56
}

57
58
void KeyframeView::slotModelChanged()
{
59
60
    int offset = pCore->getItemIn(m_model->getOwnerId());
    emit atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
61
    emit modified();
62
63
    update();
}
64

65
void KeyframeView::slotSetPosition(int pos, bool isInRange)
66
{
67
68
69
70
71
    if (!isInRange) {
        m_position = -1;
        update();
        return;
    }
72
73
    if (pos != m_position) {
        m_position = pos;
74
75
        int offset = pCore->getItemIn(m_model->getOwnerId());
        emit atKeyframe(m_model->hasKeyframe(pos + offset), m_model->singleKeyframe());
76
77
78
79
        update();
    }
}

80
81
82
83
84
void KeyframeView::initKeyframePos()
{
    emit atKeyframe(m_model->hasKeyframe(m_position), m_model->singleKeyframe());
}

85
void KeyframeView::slotAddKeyframe(int pos)
86
87
88
89
{
    if (pos < 0) {
        pos = m_position;
    }
90
91
    int offset = pCore->getItemIn(m_model->getOwnerId());
    m_model->addKeyframe(GenTime(pos + offset, pCore->getCurrentFps()), m_currentType);
92
93
}

94
void KeyframeView::slotAddRemove()
95
{
96
97
    int offset = pCore->getItemIn(m_model->getOwnerId());
    if (m_model->hasKeyframe(m_position + offset)) {
98
99
        slotRemoveKeyframe(m_position);
    } else {
100
        slotAddKeyframe(m_position);
101
102
103
    }
}

104
105
void KeyframeView::slotEditType(int type, const QPersistentModelIndex &index)
{
106
107
108
    int offset = pCore->getItemIn(m_model->getOwnerId());
    if (m_model->hasKeyframe(m_position + offset)) {
        m_model->updateKeyframeType(GenTime(m_position + offset, pCore->getCurrentFps()), type, index);
109
110
111
    }
}

112
113
114
115
116
void KeyframeView::slotRemoveKeyframe(int pos)
{
    if (pos < 0) {
        pos = m_position;
    }
117
118
    int offset = pCore->getItemIn(m_model->getOwnerId());
    m_model->removeKeyframe(GenTime(pos + offset, pCore->getCurrentFps()));
119
120
121
122
123
}

void KeyframeView::setDuration(int dur)
{
    m_duration = dur;
124
125
126
    int offset = pCore->getItemIn(m_model->getOwnerId());
    emit atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
    update();
127
128
129
130
131
132
133
134
135
}

void KeyframeView::slotGoToNext()
{
    if (m_position == m_duration) {
        return;
    }

    bool ok;
136
137
    int offset = pCore->getItemIn(m_model->getOwnerId());
    auto next = m_model->getNextKeyframe(GenTime(m_position + offset, pCore->getCurrentFps()), &ok);
138
139

    if (ok) {
140
        emit seekToPos(qMin(next.first.frames(pCore->getCurrentFps()) - offset, m_duration - 1));
141
142
    } else {
        // no keyframe after current position
143
        emit seekToPos(m_duration - 1);
144
145
146
147
148
149
150
151
152
153
    }
}

void KeyframeView::slotGoToPrev()
{
    if (m_position == 0) {
        return;
    }

    bool ok;
154
155
    int offset = pCore->getItemIn(m_model->getOwnerId());
    auto prev = m_model->getPrevKeyframe(GenTime(m_position + offset, pCore->getCurrentFps()), &ok);
156
157

    if (ok) {
158
        emit seekToPos(qMax(0, prev.first.frames(pCore->getCurrentFps()) - offset));
159
160
    } else {
        // no keyframe after current position
161
        emit seekToPos(m_duration);
162
163
164
165
166
    }
}

void KeyframeView::mousePressEvent(QMouseEvent *event)
{
167
    int offset = pCore->getItemIn(m_model->getOwnerId());
168
169
170
    int pos = event->x() / m_scale;
    if (event->y() < m_lineHeight && event->button() == Qt::LeftButton) {
        bool ok;
171
        GenTime position(pos + offset, pCore->getCurrentFps());
172
        auto keyframe = m_model->getClosestKeyframe(position, &ok);
173
        if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset) < ceil(m_lineHeight / 1.5)) {
174
            m_currentKeyframeOriginal = keyframe.first.frames(pCore->getCurrentFps()) - offset;
175
176
177
178
            // Select and seek to keyframe
            m_currentKeyframe = m_currentKeyframeOriginal;
            emit seekToPos(m_currentKeyframeOriginal);
            return;
179
180
181
182
183
        }
    }

    // no keyframe next to mouse
    m_currentKeyframe = m_currentKeyframeOriginal = -1;
184
    emit seekToPos(pos);
185
186
187
188
189
    update();
}

void KeyframeView::mouseMoveEvent(QMouseEvent *event)
{
190
    int offset = pCore->getItemIn(m_model->getOwnerId());
191
    int pos = qBound(0, (int)(event->x() / m_scale), m_duration);
192
    GenTime position(pos + offset, pCore->getCurrentFps());
193
    if ((event->buttons() & Qt::LeftButton) != 0u) {
194
195
196
        if (m_currentKeyframe == pos) {
            return;
        }
197
        if (m_currentKeyframe > 0) {
198
199
            if (!m_model->hasKeyframe(pos + offset)) {
                GenTime currentPos(m_currentKeyframe + offset, pCore->getCurrentFps());
200
201
202
203
204
                if (m_model->moveKeyframe(currentPos, position, false)) {
                    m_currentKeyframe = pos;
                }
            }
        }
205
        emit seekToPos(pos);
206
207
208
209
210
        return;
    }
    if (event->y() < m_lineHeight) {
        bool ok;
        auto keyframe = m_model->getClosestKeyframe(position, &ok);
211
        if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset) < ceil(m_lineHeight / 1.5)) {
212
            m_hoverKeyframe = keyframe.first.frames(pCore->getCurrentFps()) - offset;
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
            setCursor(Qt::PointingHandCursor);
            update();
            return;
        }
    }

    if (m_hoverKeyframe != -1) {
        m_hoverKeyframe = -1;
        setCursor(Qt::ArrowCursor);
        update();
    }
}

void KeyframeView::mouseReleaseEvent(QMouseEvent *event)
{
    Q_UNUSED(event)
    if (m_currentKeyframe >= 0) {
230
231
232
        int offset = pCore->getItemIn(m_model->getOwnerId());
        GenTime initPos(m_currentKeyframeOriginal + offset, pCore->getCurrentFps());
        GenTime targetPos(m_currentKeyframe + offset, pCore->getCurrentFps());
Nicolas Carion's avatar
Nicolas Carion committed
233
234
        bool ok1 = m_model->moveKeyframe(targetPos, initPos, false);
        bool ok2 = m_model->moveKeyframe(initPos, targetPos, true);
Nicolas Carion's avatar
Nicolas Carion committed
235
        qDebug() << "RELEASING keyframe move" << ok1 << ok2 << initPos.frames(pCore->getCurrentFps()) << targetPos.frames(pCore->getCurrentFps());
236
237
238
239
240
241
242
    }
}

void KeyframeView::mouseDoubleClickEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton && event->y() < m_lineHeight) {
        int pos = qBound(0, (int)(event->x() / m_scale), m_duration);
243
244
        int offset = pCore->getItemIn(m_model->getOwnerId());
        GenTime position(pos + offset, pCore->getCurrentFps());
245
246
        bool ok;
        auto keyframe = m_model->getClosestKeyframe(position, &ok);
247
        if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset) < ceil(m_lineHeight / 1.5)) {
248
            m_model->removeKeyframe(keyframe.first);
249
            if (keyframe.first.frames(pCore->getCurrentFps()) == m_currentKeyframe + offset) {
250
251
                m_currentKeyframe = m_currentKeyframeOriginal = -1;
            }
252
            if (keyframe.first.frames(pCore->getCurrentFps()) == m_position + offset) {
253
                emit atKeyframe(false, m_model->singleKeyframe());
254
255
256
257
258
            }
            return;
        }

        // add new keyframe
259
        m_model->addKeyframe(position, m_currentType);
260
261
262
263
264
265
266
    } else {
        QWidget::mouseDoubleClickEvent(event);
    }
}

void KeyframeView::wheelEvent(QWheelEvent *event)
{
267
268
269
270
271
272
273
274
275
    if (event->modifiers() & Qt::AltModifier) {
        if (event->delta() > 0) {
            slotGoToPrev();
        } else {
            slotGoToNext();
        }
        return;
    }
    int change = event->delta() > 0 ? -1 : 1;
276
    int pos = qBound(0, m_position + change, m_duration);
277
    emit seekToPos(pos);
278
279
280
281
282
283
284
285
286
287
}

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

    QStylePainter p(this);
    m_scale = width() / (double)(m_duration);
    // p.translate(0, m_lineHeight);
    int headOffset = m_lineHeight / 1.5;
288
    int offset = pCore->getItemIn(m_model->getOwnerId());
289
290
291
292
293

    /*
     * keyframes
     */
    for (const auto &keyframe : *m_model.get()) {
294
        int pos = keyframe.first.frames(pCore->getCurrentFps()) - offset;
295
296
297
298
299
        if (pos == m_currentKeyframe || pos == m_hoverKeyframe) {
            p.setBrush(m_colSelected);
        } else {
            p.setBrush(m_colKeyframe);
        }
300
301
302
        double scaledPos = pos * m_scale;
        p.drawLine(QPointF(scaledPos, headOffset), QPointF(scaledPos, m_lineHeight + headOffset / 2.0));
        switch (keyframe.second.first) {
Nicolas Carion's avatar
Nicolas Carion committed
303
304
305
306
307
308
309
310
311
312
313
314
315
        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;
316
        }
317
318
319
320
321
322
323
324
    }

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

    /*
     * Time-"line"
     */
    p.setPen(m_colKeyframe);
Nicolas Carion's avatar
Nicolas Carion committed
325
    p.drawLine(0, m_lineHeight + (headOffset / 2), (m_duration - 1) * m_scale, m_lineHeight + (headOffset / 2));
326
327
328
329

    /*
     * current position
     */
330
331
332
333
334
335
336
337
    if (m_position >= 0) {
        QPolygon pa(3);
        int cursorwidth = (m_size - (m_lineHeight + headOffset / 2)) / 2 + 1;
        QPolygonF position = QPolygonF() << QPointF(-cursorwidth, m_size) << QPointF(cursorwidth, m_size) << QPointF(0, m_lineHeight + (headOffset / 2) + 1);
        position.translate(m_position * m_scale, 0);
        p.setBrush(m_colKeyframe);
        p.drawPolygon(position);
    }
338
}