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