1 /*
2     SPDX-FileCopyrightText: 2011 Till Theato <root@ttill.de>
3     SPDX-FileCopyrightText: 2017 Nicolas Carion
4     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6 
7 #include "keyframewidget.hpp"
8 #include "assets/keyframes/model/rect/recthelper.hpp"
9 #include "assets/keyframes/model/corners/cornershelper.hpp"
10 #include "assets/keyframes/model/keyframemodellist.hpp"
11 #include "assets/keyframes/model/rotoscoping/rotohelper.hpp"
12 #include "assets/keyframes/model/keyframemonitorhelper.hpp"
13 #include "assets/keyframes/view/keyframeview.hpp"
14 #include "assets/model/assetparametermodel.hpp"
15 #include "assets/view/widgets/keyframeimport.h"
16 #include "core.h"
17 #include "kdenlivesettings.h"
18 #include "monitor/monitor.h"
19 #include "timecode.h"
20 #include "timecodedisplay.h"
21 #include "widgets/doublewidget.h"
22 #include "widgets/geometrywidget.h"
23 #include "lumaliftgainparam.hpp"
24 #include "effects/effectsrepository.hpp"
25 
26 #include <KSelectAction>
27 #include <KActionCategory>
28 #include <QApplication>
29 #include <QClipboard>
30 #include <QJsonDocument>
31 #include <QMenu>
32 #include <QPointer>
33 #include <QToolButton>
34 #include <QStyle>
35 #include <QVBoxLayout>
36 #include <QDialogButtonBox>
37 #include <QCheckBox>
38 #include <klocalizedstring.h>
39 #include <utility>
40 
KeyframeWidget(std::shared_ptr<AssetParameterModel> model,QModelIndex index,QSize frameSize,QWidget * parent)41 KeyframeWidget::KeyframeWidget(std::shared_ptr<AssetParameterModel> model, QModelIndex index, QSize frameSize, QWidget *parent)
42     : AbstractParamWidget(std::move(model), index, parent)
43     , m_monitorHelper(nullptr)
44     , m_neededScene(MonitorSceneType::MonitorSceneDefault)
45     , m_sourceFrameSize(frameSize.isValid() && !frameSize.isNull() ? frameSize : pCore->getCurrentFrameSize())
46     , m_baseHeight(0)
47     , m_addedHeight(0)
48 {
49     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
50     m_lay = new QVBoxLayout(this);
51     m_lay->setSpacing(0);
52 
53     bool ok = false;
54     int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok);
55     Q_ASSERT(ok);
56     m_model->prepareKeyframes();
57     m_keyframes = m_model->getKeyframeModel();
58     m_keyframeview = new KeyframeView(m_keyframes, duration, this);
59 
60 
61     m_buttonAddDelete = new QToolButton(this);
62     m_buttonAddDelete->setAutoRaise(true);
63 
64     connect(m_buttonAddDelete, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotAddRemove);
65     connect(this, &KeyframeWidget::addRemove, m_keyframeview, &KeyframeView::slotAddRemove);
66 
67     m_buttonAddDelete->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-add")));
68     m_buttonAddDelete->setToolTip(i18n("Add keyframe"));
69 
70 
71     QToolButton *buttonPrevious = new QToolButton(this);
72     buttonPrevious->setAutoRaise(true);
73     buttonPrevious->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-previous")));
74     buttonPrevious->setToolTip(i18n("Go to previous keyframe"));
75     connect(buttonPrevious, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotGoToPrev);
76     connect(this, &KeyframeWidget::goToPrevious, m_keyframeview, &KeyframeView::slotGoToPrev);
77 
78     QToolButton *buttonNext = new QToolButton(this);
79     buttonNext->setAutoRaise(true);
80     buttonNext->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-next")));
81     buttonNext->setToolTip(i18n("Go to next keyframe"));
82     connect(buttonNext, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotGoToNext);
83     connect(this, &KeyframeWidget::goToNext, m_keyframeview, &KeyframeView::slotGoToNext);
84 
85     // Move keyframe to cursor
86     m_buttonCenter = new QToolButton(this);
87     m_buttonCenter->setAutoRaise(true);
88     m_buttonCenter->setIcon(QIcon::fromTheme(QStringLiteral("align-horizontal-center")));
89     m_buttonCenter->setToolTip(i18n("Move selected keyframe to cursor"));
90 
91     // Duplicate selected keyframe at cursor pos
92     m_buttonCopy = new QToolButton(this);
93     m_buttonCopy->setAutoRaise(true);
94     m_buttonCopy->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-duplicate")));
95     m_buttonCopy->setToolTip(i18n("Duplicate selected keyframe"));
96 
97     // Apply current value to selected keyframes
98     m_buttonApply = new QToolButton(this);
99     m_buttonApply->setAutoRaise(true);
100     m_buttonApply->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste")));
101     m_buttonApply->setToolTip(i18n("Apply current position value to selected keyframes"));
102     m_buttonApply->setFocusPolicy(Qt::StrongFocus);
103 
104     // Keyframe type widget
105     m_selectType = new KSelectAction(QIcon::fromTheme(QStringLiteral("keyframes")), i18n("Keyframe interpolation"), this);
106     QAction *linear = new QAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Linear"), this);
107     linear->setData(int(mlt_keyframe_linear));
108     linear->setCheckable(true);
109     m_selectType->addAction(linear);
110     QAction *discrete = new QAction(QIcon::fromTheme(QStringLiteral("discrete")), i18n("Discrete"), this);
111     discrete->setData(int(mlt_keyframe_discrete));
112     discrete->setCheckable(true);
113     m_selectType->addAction(discrete);
114     QAction *curve = new QAction(QIcon::fromTheme(QStringLiteral("smooth")), i18n("Smooth"), this);
115     curve->setData(int(mlt_keyframe_smooth));
116     curve->setCheckable(true);
117     m_selectType->addAction(curve);
118     m_selectType->setCurrentAction(linear);
119     connect(m_selectType, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, &KeyframeWidget::slotEditKeyframeType);
120     m_selectType->setToolBarMode(KSelectAction::ComboBoxMode);
121     m_toolbar = new QToolBar(this);
122     m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
123     int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
124     m_toolbar->setIconSize(QSize(size, size));
125 
126     Monitor *monitor = pCore->getMonitor(m_model->monitorId);
127     connect(monitor, &Monitor::seekPosition, this, &KeyframeWidget::monitorSeek, Qt::UniqueConnection);
128     connect(pCore.get(), &Core::disconnectEffectStack, this, &KeyframeWidget::disconnectEffectStack);
129 
130     m_time = new TimecodeDisplay(pCore->timecode(), this);
131     m_time->setRange(0, duration - 1);
132     m_time->setOffset(m_model->data(index, AssetParameterModel::ParentInRole).toInt());
133 
134     m_toolbar->addWidget(buttonPrevious);
135     m_toolbar->addWidget(m_buttonAddDelete);
136     m_toolbar->addWidget(buttonNext);
137     m_toolbar->addWidget(m_buttonCenter);
138     m_toolbar->addWidget(m_buttonCopy);
139     m_toolbar->addWidget(m_buttonApply);
140     m_toolbar->addAction(m_selectType);
141 
142     QAction *seekKeyframe = new QAction(i18n("Seek to keyframe on select"), this);
143     seekKeyframe->setCheckable(true);
144     seekKeyframe->setChecked(KdenliveSettings::keyframeseek());
145     connect(seekKeyframe, &QAction::triggered, [&](bool selected) {
146         KdenliveSettings::setKeyframeseek(selected);
147     });
148     // copy/paste keyframes from clipboard
149     QAction *copy = new QAction(i18n("Copy keyframes to clipboard"), this);
150     connect(copy, &QAction::triggered, this, &KeyframeWidget::slotCopyKeyframes);
151     QAction *copyValue = new QAction(i18n("Copy value at cursor position to clipboard"), this);
152     connect(copyValue, &QAction::triggered, this, &KeyframeWidget::slotCopyValueAtCursorPos);
153     QAction *paste = new QAction(i18n("Import keyframes from clipboard"), this);
154     connect(paste, &QAction::triggered, this, &KeyframeWidget::slotImportKeyframes);
155     if (m_model->data(index, AssetParameterModel::TypeRole).value<ParamType>() == ParamType::ColorWheel) {
156         // TODO color wheel doesn't support keyframe import/export yet
157         copy->setVisible(false);
158         copyValue->setVisible(false);
159         paste->setVisible(false);
160     }
161     // Remove keyframes
162     QAction *removeNext = new QAction(i18n("Remove all keyframes after cursor"), this);
163     connect(removeNext, &QAction::triggered, this, &KeyframeWidget::slotRemoveNextKeyframes);
164 
165     // Default kf interpolation
166     KSelectAction *kfType = new KSelectAction(i18n("Default keyframe type"), this);
167     QAction *discrete2 = new QAction(QIcon::fromTheme(QStringLiteral("discrete")), i18n("Discrete"), this);
168     discrete2->setData(int(mlt_keyframe_discrete));
169     discrete2->setCheckable(true);
170     kfType->addAction(discrete2);
171     QAction *linear2 = new QAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Linear"), this);
172     linear2->setData(int(mlt_keyframe_linear));
173     linear2->setCheckable(true);
174     kfType->addAction(linear2);
175     QAction *curve2 = new QAction(QIcon::fromTheme(QStringLiteral("smooth")), i18n("Smooth"), this);
176     curve2->setData(int(mlt_keyframe_smooth));
177     curve2->setCheckable(true);
178     kfType->addAction(curve2);
179     switch (KdenliveSettings::defaultkeyframeinterp()) {
180     case mlt_keyframe_discrete:
181         kfType->setCurrentAction(discrete2);
182         break;
183     case mlt_keyframe_smooth:
184         kfType->setCurrentAction(curve2);
185         break;
186     default:
187         kfType->setCurrentAction(linear2);
188         break;
189     }
190     connect(kfType, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered),
191             this, [&](QAction *ac) { KdenliveSettings::setDefaultkeyframeinterp(ac->data().toInt()); });
192     auto *container = new QMenu(this);
193     container->addAction(seekKeyframe);
194     container->addAction(copy);
195     container->addAction(copyValue);
196     container->addAction(paste);
197     container->addSeparator();
198     container->addAction(kfType);
199     container->addAction(removeNext);
200 
201     // rotoscoping only supports linear keyframes
202     if (m_model->getAssetId() == QLatin1String("rotoscoping")) {
203         m_selectType->setVisible(false);
204         m_selectType->setCurrentAction(linear);
205         kfType->setVisible(false);
206         kfType->setCurrentAction(linear2);
207     }
208 
209     // Menu toolbutton
210     auto *menuButton = new QToolButton(this);
211     menuButton->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu")));
212     menuButton->setToolTip(i18n("Options"));
213     menuButton->setMenu(container);
214     menuButton->setPopupMode(QToolButton::InstantPopup);
215     m_toolbar->addWidget(menuButton);
216     m_toolbar->addWidget(m_time);
217 
218     m_lay->addWidget(m_keyframeview);
219     m_lay->addWidget(m_toolbar);
220 
221     connect(m_time, &TimecodeDisplay::timeCodeEditingFinished, this, [&]() { slotSetPosition(-1, true); });
222     connect(m_keyframeview, &KeyframeView::seekToPos, this, [&](int pos) {
223         if (pos < 0) {
224             m_time->setValue(0);
225             m_keyframeview->slotSetPosition(0, true);
226         } else {
227             int in = m_model->data(m_index, AssetParameterModel::InRole).toInt();
228             int p = qMax(0, pos - in);
229             m_time->setValue(p);
230             m_keyframeview->slotSetPosition(p, true);
231         }
232         m_buttonAddDelete->setEnabled(pos > 0);
233         slotRefreshParams();
234         emit seekToPos(pos);
235     });
236     connect(m_keyframeview, &KeyframeView::atKeyframe, this, &KeyframeWidget::slotAtKeyframe);
237     connect(m_keyframeview, &KeyframeView::modified, this, &KeyframeWidget::slotRefreshParams);
238     connect(m_keyframeview, &KeyframeView::activateEffect, this, &KeyframeWidget::activateEffect);
239 
240     connect(m_buttonCenter, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotCenterKeyframe);
241     connect(m_buttonCopy, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotDuplicateKeyframe);
242     connect(m_buttonApply, &QAbstractButton::pressed, this, [this]() {
243         QMultiMap<QPersistentModelIndex, QString> paramList;
244         QList<QPersistentModelIndex> rectParams;
245         for (const auto &w : m_parameters) {
246             auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value<ParamType>();
247             if (type == ParamType::AnimatedRect) {
248                 if (m_model->data(w.first, AssetParameterModel::OpacityRole).toBool()) {
249                     paramList.insert(w.first, i18n("Opacity"));
250                 }
251                 paramList.insert(w.first, i18n("Height"));
252                 paramList.insert(w.first, i18n("Width"));
253                 paramList.insert(w.first, i18n("Y position"));
254                 paramList.insert(w.first, i18n("X position"));
255                 rectParams << w.first;
256             } else {
257                 paramList.insert(w.first, m_model->data(w.first, Qt::DisplayRole).toString());
258             }
259         }
260         if (paramList.count() == 0) {
261             qDebug()<<"=== No parameter to copy, aborting";
262             return;
263         }
264         if (paramList.count() == 1) {
265             m_keyframeview->copyCurrentValue(m_keyframes->getIndexAtRow(0), QString());
266             return;
267         }
268         // More than one param
269         QDialog d(this);
270         QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
271         auto *l = new QVBoxLayout;
272         d.setLayout(l);
273         l->addWidget(new QLabel(i18n("Select parameters to copy"), &d));
274         QMapIterator<QPersistentModelIndex, QString> i(paramList);
275         while (i.hasNext()) {
276             i.next();
277             auto *cb = new QCheckBox(i.value(), this);
278             cb->setProperty("index", i.key());
279             l->addWidget(cb);
280         }
281         l->addWidget(buttonBox);
282         d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject);
283         d.connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept);
284         if (d.exec() != QDialog::Accepted) {
285             return;
286         }
287         paramList.clear();
288         QList<QCheckBox *> cbs = d.findChildren<QCheckBox *>();
289         QMap<QPersistentModelIndex, QStringList> params;
290         for (auto c : qAsConst(cbs)) {
291             //qDebug()<<"=== FOUND CBS: "<<KLocalizedString::removeAcceleratorMarker(c->text());
292             if (c->isChecked()) {
293                 QPersistentModelIndex ix = c->property("index").toModelIndex();
294                 if (rectParams.contains(ix)) {
295                     // Check param name
296                     QString cbName = KLocalizedString::removeAcceleratorMarker(c->text());
297                     if (cbName == i18n("Opacity")) {
298                         if (params.contains(ix)) {
299                             params[ix] << QStringLiteral("spinO");
300                         } else {
301                             params.insert(ix, {QStringLiteral("spinO")});
302                         }
303                     } else if (cbName == i18n("Height")) {
304                         if (params.contains(ix)) {
305                             params[ix] << QStringLiteral("spinH");
306                         } else {
307                             params.insert(ix, {QStringLiteral("spinH")});
308                         }
309                     } else if (cbName == i18n("Width")) {
310                         if (params.contains(ix)) {
311                             params[ix] << QStringLiteral("spinW");
312                         } else {
313                             params.insert(ix, {QStringLiteral("spinW")});
314                         }
315                     } else if (cbName == i18n("X position")) {
316                         if (params.contains(ix)) {
317                             params[ix] << QStringLiteral("spinX");
318                         } else {
319                             params.insert(ix, {QStringLiteral("spinX")});
320                         }
321                     } else if (cbName == i18n("Y position")) {
322                         if (params.contains(ix)) {
323                             params[ix] << QStringLiteral("spinY");
324                         } else {
325                             params.insert(ix, {QStringLiteral("spinY")});
326                         }
327                     }
328                     if (!params.contains(ix)) {
329                         params.insert(ix, {});
330                     }
331                 }
332             }
333         }
334         QMapIterator<QPersistentModelIndex, QStringList> p(params);
335         while (p.hasNext()) {
336             p.next();
337             m_keyframeview->copyCurrentValue(p.key(), p.value().join(QLatin1Char(' ')));
338         }
339         return;
340     });
341     //m_baseHeight = m_keyframeview->height() + m_selectType->defaultWidget()->sizeHint().height();
342     QMargins mrg = m_lay->contentsMargins();
343     m_baseHeight = m_keyframeview->height() + m_toolbar->sizeHint().height();
344     m_addedHeight = mrg.top() + mrg.bottom();
345     setFixedHeight(m_baseHeight + m_addedHeight);
346     addParameter(index);
347 }
348 
~KeyframeWidget()349 KeyframeWidget::~KeyframeWidget()
350 {
351     delete m_keyframeview;
352     delete m_buttonAddDelete;
353     delete m_time;
354 }
355 
disconnectEffectStack()356 void KeyframeWidget::disconnectEffectStack()
357 {
358     Monitor *monitor = pCore->getMonitor(m_model->monitorId);
359     disconnect(monitor, &Monitor::seekPosition, this, &KeyframeWidget::monitorSeek);
360 }
361 
monitorSeek(int pos)362 void KeyframeWidget::monitorSeek(int pos)
363 {
364     int in = 0;
365     int out = 0;
366     bool canHaveZone = m_model->getOwnerId().first == ObjectType::Master || m_model->getOwnerId().first == ObjectType::TimelineTrack;
367     if (canHaveZone) {
368         bool ok = false;
369         in = m_model->data(m_index, AssetParameterModel::InRole).toInt(&ok);
370         out = m_model->data(m_index, AssetParameterModel::OutRole).toInt(&ok);
371         Q_ASSERT(ok);
372     }
373     if (in == 0 && out == 0) {
374         in = pCore->getItemPosition(m_model->getOwnerId());
375         out = in + pCore->getItemDuration(m_model->getOwnerId());
376     }
377     bool isInRange = pos >= in && pos < out;
378     connectMonitor(isInRange && m_model->isActive());
379     m_buttonAddDelete->setEnabled(isInRange && pos > in);
380     int framePos = qBound(in, pos, out) - in;
381     if (isInRange && framePos != m_time->getValue()) {
382         slotSetPosition(framePos, false);
383     }
384 }
385 
slotEditKeyframeType(QAction * action)386 void KeyframeWidget::slotEditKeyframeType(QAction *action)
387 {
388     int type = action->data().toInt();
389     m_keyframeview->slotEditType(type, m_index);
390     emit activateEffect();
391 }
392 
slotRefreshParams()393 void KeyframeWidget::slotRefreshParams()
394 {
395     int pos = getPosition();
396     KeyframeType keyType = m_keyframes->keyframeType(GenTime(pos, pCore->getCurrentFps()));
397     int i = 0;
398     while (auto ac = m_selectType->action(i)) {
399         if (ac->data().toInt() == int(keyType)) {
400             m_selectType->setCurrentItem(i);
401             break;
402         }
403         i++;
404     }
405     for (const auto &w : m_parameters) {
406         auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value<ParamType>();
407         if (type == ParamType::KeyframeParam) {
408             (static_cast<DoubleWidget *>(w.second))->setValue(m_keyframes->getInterpolatedValue(pos, w.first).toDouble());
409         } else if (type == ParamType::AnimatedRect) {
410             const QString val = m_keyframes->getInterpolatedValue(pos, w.first).toString();
411             const QStringList vals = val.split(QLatin1Char(' '));
412             QRect rect;
413             double opacity = -1;
414             if (vals.count() >= 4) {
415                 rect = QRect(vals.at(0).toInt(), vals.at(1).toInt(), vals.at(2).toInt(), vals.at(3).toInt());
416                 if (vals.count() > 4) {
417                     opacity = vals.at(4).toDouble();
418                 }
419             }
420             (static_cast<GeometryWidget *>(w.second))->setValue(rect, opacity);
421         } else if (type == ParamType::ColorWheel) {
422             (static_cast<LumaLiftGainParam *>(w.second)->slotRefresh(pos));
423         }
424     }
425     if (m_monitorHelper && m_model->isActive()) {
426         m_monitorHelper->refreshParams(pos);
427         return;
428     }
429 }
slotSetPosition(int pos,bool update)430 void KeyframeWidget::slotSetPosition(int pos, bool update)
431 {
432     if (pos < 0) {
433         pos = m_time->getValue();
434     } else {
435         m_time->setValue(pos);
436     }
437     m_keyframeview->slotSetPosition(pos, true);
438     m_buttonAddDelete->setEnabled(pos > 0);
439     slotRefreshParams();
440 
441     if (update) {
442         //int in = m_model->data(m_index, AssetParameterModel::InRole).toInt();
443         emit seekToPos(pos);
444     }
445 }
446 
getPosition() const447 int KeyframeWidget::getPosition() const
448 {
449     return m_time->getValue() + pCore->getItemIn(m_model->getOwnerId());
450 }
451 
updateTimecodeFormat()452 void KeyframeWidget::updateTimecodeFormat()
453 {
454     m_time->slotUpdateTimeCodeFormat();
455 }
456 
slotAtKeyframe(bool atKeyframe,bool singleKeyframe)457 void KeyframeWidget::slotAtKeyframe(bool atKeyframe, bool singleKeyframe)
458 {
459     if (atKeyframe) {
460         m_buttonAddDelete->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-remove")));
461         m_buttonAddDelete->setToolTip(i18n("Delete keyframe"));
462     } else {
463         m_buttonAddDelete->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-add")));
464         m_buttonAddDelete->setToolTip(i18n("Add keyframe"));
465     }
466     m_buttonCenter->setEnabled(!atKeyframe);
467     m_buttonCopy->setEnabled(!atKeyframe);
468     emit updateEffectKeyframe(atKeyframe || singleKeyframe);
469     m_selectType->setEnabled(atKeyframe || singleKeyframe);
470     for (const auto &w : m_parameters) {
471         w.second->setEnabled(atKeyframe || singleKeyframe);
472     }
473 }
474 
slotRefresh()475 void KeyframeWidget::slotRefresh()
476 {
477     // update duration
478     bool ok = false;
479     int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok);
480     Q_ASSERT(ok);
481     int in = m_model->data(m_index, AssetParameterModel::InRole).toInt(&ok);
482     // m_model->dataChanged(QModelIndex(), QModelIndex());
483     //->getKeyframeModel()->getKeyModel(m_index)->dataChanged(QModelIndex(), QModelIndex());
484     m_keyframeview->setDuration(duration, in);
485     m_time->setRange(0, duration - 1);
486     m_time->setOffset(in);
487     slotRefreshParams();
488 }
489 
resetKeyframes()490 void KeyframeWidget::resetKeyframes()
491 {
492     // update duration
493     bool ok = false;
494     int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok);
495     Q_ASSERT(ok);
496     int in = m_model->data(m_index, AssetParameterModel::InRole).toInt(&ok);
497     // reset keyframes
498     m_keyframes->refresh();
499     // m_model->dataChanged(QModelIndex(), QModelIndex());
500     m_keyframeview->setDuration(duration, in);
501     m_time->setRange(0, duration - 1);
502     m_time->setOffset(in);
503     slotRefreshParams();
504 }
505 
addParameter(const QPersistentModelIndex & index)506 void KeyframeWidget::addParameter(const QPersistentModelIndex &index)
507 {
508     // Retrieve parameters from the model
509     QString name = m_model->data(index, Qt::DisplayRole).toString();
510     QString comment = m_model->data(index, AssetParameterModel::CommentRole).toString();
511     QString suffix = m_model->data(index, AssetParameterModel::SuffixRole).toString();
512 
513     auto type = m_model->data(index, AssetParameterModel::TypeRole).value<ParamType>();
514     // Construct object
515     QWidget *paramWidget = nullptr;
516     if (type == ParamType::AnimatedRect) {
517         m_neededScene = MonitorSceneType::MonitorSceneGeometry;
518         int inPos = m_model->data(index, AssetParameterModel::ParentInRole).toInt();
519         QPair<int, int> range(inPos, inPos + m_model->data(index, AssetParameterModel::ParentDurationRole).toInt());
520         const QString value = m_keyframes->getInterpolatedValue(getPosition(), index).toString();
521         m_monitorHelper = new KeyframeMonitorHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this);
522         QRect rect;
523         double opacity = 0;
524         QStringList vals = value.split(QLatin1Char(' '));
525         if (vals.count() > 3) {
526             rect = QRect(vals.at(0).toInt(), vals.at(1).toInt(), vals.at(2).toInt(), vals.at(3).toInt());
527             if (vals.count() > 4) {
528                 opacity = vals.at(4).toDouble();
529             }
530         }
531         // qtblend uses an opacity value in the (0-1) range, while older geometry effects use (0-100)
532         GeometryWidget *geomWidget = new GeometryWidget(pCore->getMonitor(m_model->monitorId), range, rect, opacity, m_sourceFrameSize, false,
533                                                         m_model->data(m_index, AssetParameterModel::OpacityRole).toBool(), this);
534         connect(geomWidget, &GeometryWidget::valueChanged,
535                 this, [this, index](const QString v) {
536                     emit activateEffect();
537                     m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index); });
538         connect(geomWidget, &GeometryWidget::updateMonitorGeometry, this, [this](const QRect r) {
539                     if (m_model->isActive()) {
540                         pCore->getMonitor(m_model->monitorId)->setUpEffectGeometry(r);
541                     }
542         });
543         paramWidget = geomWidget;
544     } else if (type == ParamType::ColorWheel) {
545         auto colorWheelWidget = new LumaLiftGainParam(m_model, index, this);
546         connect(colorWheelWidget, &LumaLiftGainParam::valuesChanged,
547                 this, [this, index](const QList <QModelIndex> indexes, const QStringList& list, bool) {
548             emit activateEffect();
549             auto *parentCommand = new QUndoCommand();
550             parentCommand->setText(i18n("Edit %1 keyframe", EffectsRepository::get()->getName(m_model->getAssetId())));
551             for (int i = 0; i < indexes.count(); i++) {
552                 if (m_keyframes->getInterpolatedValue(getPosition(), indexes.at(i)) != list.at(i)) {
553                     m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(list.at(i)), indexes.at(i), parentCommand);
554                 }
555             }
556             if (parentCommand->childCount() > 0) {
557                 pCore->pushUndo(parentCommand);
558             }
559         });
560         connect(colorWheelWidget, &LumaLiftGainParam::updateHeight, this, [&](int h){
561             setFixedHeight(m_baseHeight + m_addedHeight + h);
562             emit updateHeight();
563         });
564         paramWidget = colorWheelWidget;
565     } else if (type == ParamType::Roto_spline) {
566         m_monitorHelper = new RotoHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this);
567         m_neededScene = MonitorSceneType::MonitorSceneRoto;
568     } else {
569         if (m_model->getAssetId() == QLatin1String("frei0r.c0rners")) {
570             if (m_neededScene == MonitorSceneDefault && !m_monitorHelper) {
571                 m_neededScene = MonitorSceneType::MonitorSceneCorners;
572                 m_monitorHelper = new CornersHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this);
573                 connect(this, &KeyframeWidget::addIndex, m_monitorHelper, &CornersHelper::addIndex);
574             } else {
575                 if (type == ParamType::KeyframeParam) {
576                     int paramName = m_model->data(index, AssetParameterModel::NameRole).toInt();
577                     if (paramName < 8) {
578                         emit addIndex(index);
579                     }
580                 }
581             }
582         }
583         if(m_model->getAssetId().contains(QLatin1String("frei0r.alphaspot"))) {
584             if (m_neededScene == MonitorSceneDefault && !m_monitorHelper) {
585                 m_neededScene = MonitorSceneType::MonitorSceneGeometry;
586                 m_monitorHelper = new RectHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this);
587                 connect(this, &KeyframeWidget::addIndex, m_monitorHelper, &RectHelper::addIndex);
588             } else {
589                 if (type == ParamType::KeyframeParam) {
590                     QString paramName = m_model->data(index, AssetParameterModel::NameRole).toString();
591                     if (paramName.contains(QLatin1String("Position X")) || paramName.contains(QLatin1String("Position Y")) ||
592                             paramName.contains(QLatin1String("Size X")) || paramName.contains(QLatin1String("Size Y"))) {
593                         emit addIndex(index);
594                     }
595                 }
596             }
597         }
598         double value = m_keyframes->getInterpolatedValue(getPosition(), index).toDouble();
599         double min = m_model->data(index, AssetParameterModel::MinRole).toDouble();
600         double max = m_model->data(index, AssetParameterModel::MaxRole).toDouble();
601         double defaultValue = m_model->data(index, AssetParameterModel::DefaultRole).toDouble();
602         int decimals = m_model->data(index, AssetParameterModel::DecimalsRole).toInt();
603         double factor = m_model->data(index, AssetParameterModel::FactorRole).toDouble();
604         factor = qFuzzyIsNull(factor) ? 1 : factor;
605         auto doubleWidget = new DoubleWidget(name, value, min, max, factor, defaultValue, comment, -1, suffix, decimals, m_model->data(index, AssetParameterModel::OddRole).toBool(), this);
606         connect(doubleWidget, &DoubleWidget::valueChanged,
607                 this, [this, index](double v) {
608             emit activateEffect();
609             m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index);
610         });
611         doubleWidget->setDragObjectName(QString::number(index.row()));
612         paramWidget = doubleWidget;
613     }
614     if (paramWidget) {
615         m_parameters[index] = paramWidget;
616         m_lay->addWidget(paramWidget);
617         m_addedHeight += paramWidget->minimumHeight();
618         setFixedHeight(m_baseHeight + m_addedHeight);
619     }
620 }
621 
slotInitMonitor(bool active)622 void KeyframeWidget::slotInitMonitor(bool active)
623 {
624     connectMonitor(active);
625     Monitor *monitor = pCore->getMonitor(m_model->monitorId);
626     if (m_keyframeview) {
627         m_keyframeview->initKeyframePos();
628         connect(monitor, &Monitor::updateScene, m_keyframeview, &KeyframeView::slotModelChanged, Qt::UniqueConnection);
629     }
630 }
631 
connectMonitor(bool active)632 void KeyframeWidget::connectMonitor(bool active)
633 {
634     if (m_monitorHelper) {
635         if (m_model->isActive()) {
636             connect(m_monitorHelper, &KeyframeMonitorHelper::updateKeyframeData, this, &KeyframeWidget::slotUpdateKeyframesFromMonitor, Qt::UniqueConnection);
637             if (m_monitorHelper->connectMonitor(active)) {
638                 slotRefreshParams();
639             }
640         } else {
641             m_monitorHelper->connectMonitor(false);
642             disconnect(m_monitorHelper, &KeyframeMonitorHelper::updateKeyframeData, this, &KeyframeWidget::slotUpdateKeyframesFromMonitor);
643         }
644     }
645     Monitor *monitor = pCore->getMonitor(m_model->monitorId);
646     if (active) {
647         connect(monitor, &Monitor::seekToNextKeyframe, m_keyframeview, &KeyframeView::slotGoToNext, Qt::UniqueConnection);
648         connect(monitor, &Monitor::seekToPreviousKeyframe, m_keyframeview, &KeyframeView::slotGoToPrev, Qt::UniqueConnection);
649         connect(monitor, &Monitor::addRemoveKeyframe, m_keyframeview, &KeyframeView::slotAddRemove, Qt::UniqueConnection);
650         connect(this, &KeyframeWidget::updateEffectKeyframe, monitor, &Monitor::setEffectKeyframe, Qt::DirectConnection);
651         connect(monitor, &Monitor::seekToKeyframe, this, &KeyframeWidget::slotSeekToKeyframe, Qt::UniqueConnection);
652     } else {
653         disconnect(monitor, &Monitor::seekToNextKeyframe, m_keyframeview, &KeyframeView::slotGoToNext);
654         disconnect(monitor, &Monitor::seekToPreviousKeyframe, m_keyframeview, &KeyframeView::slotGoToPrev);
655         disconnect(monitor, &Monitor::addRemoveKeyframe, m_keyframeview, &KeyframeView::slotAddRemove);
656         disconnect(this, &KeyframeWidget::updateEffectKeyframe, monitor, &Monitor::setEffectKeyframe);
657         disconnect(monitor, &Monitor::seekToKeyframe, this, &KeyframeWidget::slotSeekToKeyframe);
658     }
659     for (const auto &w : m_parameters) {
660         auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value<ParamType>();
661         if (type == ParamType::AnimatedRect) {
662             (static_cast<GeometryWidget *>(w.second))->connectMonitor(active);
663             break;
664         }
665     }
666 }
667 
slotUpdateKeyframesFromMonitor(const QPersistentModelIndex & index,const QVariant & res)668 void KeyframeWidget::slotUpdateKeyframesFromMonitor(const QPersistentModelIndex &index, const QVariant &res)
669 {
670     emit activateEffect();
671     if (m_keyframes->isEmpty()) {
672         GenTime pos(pCore->getItemIn(m_model->getOwnerId()) + m_time->getValue(), pCore->getCurrentFps());
673         if (m_time->getValue() > 0) {
674             GenTime pos0(pCore->getItemIn(m_model->getOwnerId()), pCore->getCurrentFps());
675             m_keyframes->addKeyframe(pos0, KeyframeType::Linear);
676             m_keyframes->updateKeyframe(pos0, res, index);
677         }
678         m_keyframes->addKeyframe(pos, KeyframeType::Linear);
679         m_keyframes->updateKeyframe(pos, res, index);
680     } else if (m_keyframes->hasKeyframe(getPosition()) || m_keyframes->singleKeyframe()) {
681         GenTime pos(getPosition(), pCore->getCurrentFps());
682         if (m_keyframes->singleKeyframe() && KdenliveSettings::autoKeyframe() && m_neededScene == MonitorSceneType::MonitorSceneRoto) {
683             m_keyframes->addKeyframe(pos, KeyframeType::Linear);
684         }
685         m_keyframes->updateKeyframe(pos, res, index);
686     } else {
687         qDebug()<<"==== NO KFR AT: "<<getPosition();
688     }
689 }
690 
requiredScene() const691 MonitorSceneType KeyframeWidget::requiredScene() const
692 {
693     qDebug() << "// // // RESULTING REQUIRED SCENE: " << m_neededScene;
694     return m_neededScene;
695 }
696 
keyframesVisible() const697 bool KeyframeWidget::keyframesVisible() const
698 {
699     return m_keyframeview->isVisible();
700 }
701 
showKeyframes(bool enable)702 void KeyframeWidget::showKeyframes(bool enable)
703 {
704     if (enable && m_toolbar->isVisible()) {
705         return;
706     }
707     m_toolbar->setVisible(enable);
708     m_keyframeview->setVisible(enable);
709     setFixedHeight(m_addedHeight + (enable ? m_baseHeight : 0));
710 }
711 
slotCopyKeyframes()712 void KeyframeWidget::slotCopyKeyframes()
713 {
714     QJsonDocument effectDoc = m_model->toJson(false);
715     if (effectDoc.isEmpty()) {
716         return;
717     }
718     QClipboard *clipboard = QApplication::clipboard();
719     clipboard->setText(QString(effectDoc.toJson()));
720 }
721 
slotCopyValueAtCursorPos()722 void KeyframeWidget::slotCopyValueAtCursorPos()
723 {
724     QJsonDocument effectDoc = m_model->valueAsJson(getPosition(), false);
725     if (effectDoc.isEmpty()) {
726         return;
727     }
728     QClipboard *clipboard = QApplication::clipboard();
729     clipboard->setText(QString(effectDoc.toJson()));
730 }
731 
slotImportKeyframes()732 void KeyframeWidget::slotImportKeyframes()
733 {
734     QClipboard *clipboard = QApplication::clipboard();
735     QString values = clipboard->text();
736     QList<QPersistentModelIndex> indexes;
737     for (const auto &w : m_parameters) {
738         indexes << w.first;
739     }
740     if (m_neededScene == MonitorSceneRoto) {
741         indexes << m_monitorHelper->getIndexes();
742     }
743     QPointer<KeyframeImport> import = new KeyframeImport(values, m_model, indexes, m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(), m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(), this);
744     import->show();
745     connect(import, &KeyframeImport::updateQmlView, this, &KeyframeWidget::slotRefreshParams);
746 }
747 
slotRemoveNextKeyframes()748 void KeyframeWidget::slotRemoveNextKeyframes()
749 {
750     int pos = m_time->getValue() + m_model->data(m_index, AssetParameterModel::ParentInRole).toInt();
751     m_keyframes->removeNextKeyframes(GenTime(pos, pCore->getCurrentFps()));
752 }
753 
754 
slotSeekToKeyframe(int ix)755 void KeyframeWidget::slotSeekToKeyframe(int ix)
756 {
757     int pos = m_keyframes->getPosAtIndex(ix).frames(pCore->getCurrentFps());
758     slotSetPosition(pos, true);
759 }
760