File indexing completed on 2024-05-12 04:52:54
0001 /* 0002 SPDX-FileCopyrightText: 2011 Till Theato <root@ttill.de> 0003 SPDX-FileCopyrightText: 2017 Nicolas Carion 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "keyframewidget.hpp" 0008 #include "assets/keyframes/model/corners/cornershelper.hpp" 0009 #include "assets/keyframes/model/keyframemodel.hpp" 0010 #include "assets/keyframes/model/keyframemodellist.hpp" 0011 #include "assets/keyframes/model/rect/recthelper.hpp" 0012 #include "assets/keyframes/model/rotoscoping/rotohelper.hpp" 0013 #include "assets/keyframes/view/keyframeview.hpp" 0014 #include "assets/model/assetparametermodel.hpp" 0015 #include "assets/view/widgets/keyframeimport.h" 0016 #include "core.h" 0017 #include "effects/effectsrepository.hpp" 0018 #include "kdenlivesettings.h" 0019 #include "lumaliftgainparam.hpp" 0020 #include "monitor/monitor.h" 0021 #include "utils/timecode.h" 0022 #include "widgets/choosecolorwidget.h" 0023 #include "widgets/doublewidget.h" 0024 #include "widgets/geometrywidget.h" 0025 #include "widgets/timecodedisplay.h" 0026 0027 #include <KActionCategory> 0028 #include <KActionMenu> 0029 #include <KDualAction> 0030 #include <KLocalizedString> 0031 #include <KSelectAction> 0032 #include <KStandardAction> 0033 #include <QApplication> 0034 #include <QCheckBox> 0035 #include <QClipboard> 0036 #include <QDialogButtonBox> 0037 #include <QJsonArray> 0038 #include <QJsonDocument> 0039 #include <QJsonObject> 0040 #include <QJsonValue> 0041 #include <QMenu> 0042 #include <QPointer> 0043 #include <QStyle> 0044 #include <QToolButton> 0045 #include <QVBoxLayout> 0046 #include <kwidgetsaddons_version.h> 0047 #include <utility> 0048 0049 KeyframeWidget::KeyframeWidget(std::shared_ptr<AssetParameterModel> model, QModelIndex index, QSize frameSize, QWidget *parent) 0050 : AbstractParamWidget(std::move(model), index, parent) 0051 , m_monitorHelper(nullptr) 0052 , m_neededScene(MonitorSceneType::MonitorSceneDefault) 0053 , m_sourceFrameSize(frameSize.isValid() && !frameSize.isNull() ? frameSize : pCore->getCurrentFrameSize()) 0054 , m_baseHeight(0) 0055 , m_addedHeight(0) 0056 { 0057 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 0058 m_lay = new QVBoxLayout(this); 0059 m_lay->setSpacing(0); 0060 0061 bool ok = false; 0062 int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); 0063 Q_ASSERT(ok); 0064 m_model->prepareKeyframes(); 0065 m_keyframes = m_model->getKeyframeModel(); 0066 m_keyframeview = new KeyframeView(m_keyframes, duration, this); 0067 0068 m_addDeleteAction = new KDualAction(this); 0069 m_addDeleteAction->setActiveIcon(QIcon::fromTheme(QStringLiteral("keyframe-add"))); 0070 m_addDeleteAction->setActiveText(i18n("Add keyframe")); 0071 m_addDeleteAction->setInactiveIcon(QIcon::fromTheme(QStringLiteral("keyframe-remove"))); 0072 m_addDeleteAction->setInactiveText(i18n("Delete keyframe")); 0073 0074 connect(m_addDeleteAction, &KDualAction::triggered, m_keyframeview, &KeyframeView::slotAddRemove); 0075 connect(this, &KeyframeWidget::addRemove, m_keyframeview, &KeyframeView::slotAddRemove); 0076 0077 auto *previousKFAction = new QAction(QIcon::fromTheme(QStringLiteral("keyframe-previous")), i18n("Go to previous keyframe"), this); 0078 connect(previousKFAction, &QAction::triggered, m_keyframeview, &KeyframeView::slotGoToPrev); 0079 connect(this, &KeyframeWidget::goToPrevious, m_keyframeview, &KeyframeView::slotGoToPrev); 0080 0081 auto *nextKFAction = new QAction(QIcon::fromTheme(QStringLiteral("keyframe-next")), i18n("Go to next keyframe"), this); 0082 connect(nextKFAction, &QAction::triggered, m_keyframeview, &KeyframeView::slotGoToNext); 0083 connect(this, &KeyframeWidget::goToNext, m_keyframeview, &KeyframeView::slotGoToNext); 0084 0085 // Move keyframe to cursor 0086 m_centerAction = new QAction(QIcon::fromTheme(QStringLiteral("align-horizontal-center")), i18n("Move selected keyframe to cursor"), this); 0087 0088 // Apply current value to selected keyframes 0089 m_copyAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy keyframes"), this); 0090 connect(m_copyAction, &QAction::triggered, this, &KeyframeWidget::slotCopySelectedKeyframes); 0091 m_copyAction->setToolTip(i18n("Copy keyframes")); 0092 m_copyAction->setWhatsThis( 0093 xi18nc("@info:whatsthis", "Copy keyframes. Copy the selected keyframes, or current parameters values if no keyframe is selected.")); 0094 0095 m_pasteAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("Paste keyframe"), this); 0096 connect(m_pasteAction, &QAction::triggered, this, &KeyframeWidget::slotPasteKeyframeFromClipBoard); 0097 m_pasteAction->setToolTip(i18n("Paste keyframes")); 0098 m_pasteAction->setWhatsThis(xi18nc("@info:whatsthis", "Paste keyframes. Paste clipboard data as keyframes at current position.")); 0099 0100 auto *applyAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("Apply current position value to selected keyframes"), this); 0101 0102 // Keyframe type widget 0103 m_selectType = new KSelectAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Keyframe interpolation"), this); 0104 QMap<KeyframeType, QAction *> kfTypeHandles; 0105 for (auto it = KeyframeTypeName.cbegin(); it != KeyframeTypeName.cend(); it++) { // Order is fixed due to the nature of <map> 0106 QAction *tmp = new QAction(QIcon::fromTheme(KeyframeModel::getIconByKeyframeType(it.key())), it.value(), this); 0107 tmp->setData(int(it.key())); 0108 tmp->setCheckable(true); 0109 kfTypeHandles.insert(it.key(), tmp); 0110 m_selectType->addAction(kfTypeHandles[it.key()]); 0111 } 0112 m_selectType->setCurrentAction(kfTypeHandles[KeyframeType::Linear]); 0113 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 240, 0) 0114 connect(m_selectType, &KSelectAction::actionTriggered, this, &KeyframeWidget::slotEditKeyframeType); 0115 #else 0116 connect(m_selectType, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, &KeyframeWidget::slotEditKeyframeType); 0117 #endif 0118 m_selectType->setToolBarMode(KSelectAction::MenuMode); 0119 m_selectType->setToolTip(i18n("Keyframe interpolation")); 0120 m_selectType->setWhatsThis(xi18nc("@info:whatsthis", "Keyframe interpolation. This defines which interpolation will be used for the current keyframe.")); 0121 0122 m_toolbar = new QToolBar(this); 0123 m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); 0124 int size = style()->pixelMetric(QStyle::PM_SmallIconSize); 0125 m_toolbar->setIconSize(QSize(size, size)); 0126 0127 Monitor *monitor = pCore->getMonitor(m_model->monitorId); 0128 connect(monitor, &Monitor::seekPosition, this, &KeyframeWidget::monitorSeek, Qt::UniqueConnection); 0129 connect(pCore.get(), &Core::disconnectEffectStack, this, &KeyframeWidget::disconnectEffectStack); 0130 0131 m_time = new TimecodeDisplay(this); 0132 m_time->setRange(0, duration - 1); 0133 0134 m_toolbar->addAction(previousKFAction); 0135 m_toolbar->addAction(m_addDeleteAction); 0136 m_toolbar->addAction(nextKFAction); 0137 m_toolbar->addAction(m_centerAction); 0138 m_toolbar->addAction(m_copyAction); 0139 m_toolbar->addAction(m_pasteAction); 0140 m_toolbar->addAction(m_selectType); 0141 0142 QAction *seekKeyframe = new QAction(i18n("Seek to Keyframe on Select"), this); 0143 seekKeyframe->setCheckable(true); 0144 seekKeyframe->setChecked(KdenliveSettings::keyframeseek()); 0145 connect(seekKeyframe, &QAction::triggered, [&](bool selected) { KdenliveSettings::setKeyframeseek(selected); }); 0146 // copy/paste keyframes from clipboard 0147 QAction *copy = new QAction(i18n("Copy All Keyframes to Clipboard"), this); 0148 connect(copy, &QAction::triggered, this, &KeyframeWidget::slotCopyKeyframes); 0149 QAction *paste = new QAction(i18n("Import Keyframes from Clipboard…"), this); 0150 connect(paste, &QAction::triggered, this, &KeyframeWidget::slotImportKeyframes); 0151 if (m_model->data(index, AssetParameterModel::TypeRole).value<ParamType>() == ParamType::ColorWheel) { 0152 // TODO color wheel doesn't support keyframe import/export yet 0153 copy->setVisible(false); 0154 paste->setVisible(false); 0155 } 0156 // Remove keyframes 0157 QAction *removeNext = new QAction(i18n("Remove all Keyframes After Cursor"), this); 0158 connect(removeNext, &QAction::triggered, this, &KeyframeWidget::slotRemoveNextKeyframes); 0159 0160 // Default kf interpolation 0161 KSelectAction *kfType = new KSelectAction(i18n("Default Keyframe Type"), this); 0162 QAction *discrete2 = 0163 new QAction(QIcon::fromTheme(KeyframeModel::getIconByKeyframeType(KeyframeType::Discrete)), KeyframeTypeName.value(KeyframeType::Discrete), this); 0164 discrete2->setData(int(KeyframeType::Discrete)); 0165 discrete2->setCheckable(true); 0166 kfType->addAction(discrete2); 0167 QAction *linear2 = 0168 new QAction(QIcon::fromTheme(KeyframeModel::getIconByKeyframeType(KeyframeType::Linear)), KeyframeTypeName.value(KeyframeType::Linear), this); 0169 linear2->setData(int(KeyframeType::Linear)); 0170 linear2->setCheckable(true); 0171 kfType->addAction(linear2); 0172 QAction *curve2 = 0173 new QAction(QIcon::fromTheme(KeyframeModel::getIconByKeyframeType(KeyframeType::Curve)), KeyframeTypeName.value(KeyframeType::Curve), this); 0174 curve2->setData(int(KeyframeType::Curve)); 0175 curve2->setCheckable(true); 0176 kfType->addAction(curve2); 0177 switch (KdenliveSettings::defaultkeyframeinterp()) { 0178 case int(KeyframeType::Discrete): 0179 kfType->setCurrentAction(discrete2); 0180 break; 0181 case int(KeyframeType::Curve): 0182 kfType->setCurrentAction(curve2); 0183 break; 0184 default: 0185 kfType->setCurrentAction(linear2); 0186 break; 0187 } 0188 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 240, 0) 0189 connect(kfType, &KSelectAction::actionTriggered, this, [&](QAction *ac) { KdenliveSettings::setDefaultkeyframeinterp(ac->data().toInt()); }); 0190 #else 0191 connect(kfType, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, 0192 [&](QAction *ac) { KdenliveSettings::setDefaultkeyframeinterp(ac->data().toInt()); }); 0193 #endif 0194 0195 // rotoscoping only supports linear keyframes 0196 if (m_model->getAssetId() == QLatin1String("rotoscoping")) { 0197 m_selectType->setVisible(false); 0198 m_selectType->setCurrentAction(kfTypeHandles[KeyframeType::Linear]); 0199 kfType->setVisible(false); 0200 kfType->setCurrentAction(linear2); 0201 } 0202 0203 // Menu toolbutton 0204 auto *menuAction = new KActionMenu(QIcon::fromTheme(QStringLiteral("application-menu")), i18n("Options"), this); 0205 menuAction->setWhatsThis( 0206 xi18nc("@info:whatsthis", "Opens a list of further actions for managing keyframes (for example: copy to and pasting keyframes from clipboard).")); 0207 menuAction->setPopupMode(QToolButton::InstantPopup); 0208 menuAction->addAction(seekKeyframe); 0209 menuAction->addAction(copy); 0210 menuAction->addAction(paste); 0211 menuAction->addAction(applyAction); 0212 menuAction->addSeparator(); 0213 menuAction->addAction(kfType); 0214 menuAction->addAction(removeNext); 0215 m_toolbar->addAction(menuAction); 0216 0217 m_lay->addWidget(m_keyframeview); 0218 auto *hlay = new QHBoxLayout; 0219 hlay->addWidget(m_toolbar); 0220 hlay->addWidget(m_time); 0221 hlay->addStretch(); 0222 m_lay->addLayout(hlay); 0223 0224 connect(m_time, &TimecodeDisplay::timeCodeEditingFinished, this, [&]() { slotSetPosition(-1, true); }); 0225 connect(m_keyframeview, &KeyframeView::seekToPos, this, [&](int pos) { 0226 int in = m_model->data(m_index, AssetParameterModel::InRole).toInt(); 0227 bool canHaveZone = m_model->getOwnerId().type == KdenliveObjectType::Master || m_model->getOwnerId().type == KdenliveObjectType::TimelineTrack; 0228 if (pos < 0) { 0229 m_time->setValue(0); 0230 m_keyframeview->slotSetPosition(0, true); 0231 } else { 0232 m_time->setValue(qMax(0, pos - in)); 0233 m_keyframeview->slotSetPosition(pos, true); 0234 } 0235 m_addDeleteAction->setEnabled(pos > 0); 0236 slotRefreshParams(); 0237 0238 Q_EMIT seekToPos(pos + (canHaveZone ? in : 0)); 0239 }); 0240 connect(m_keyframeview, &KeyframeView::atKeyframe, this, &KeyframeWidget::slotAtKeyframe); 0241 connect(m_keyframeview, &KeyframeView::modified, this, &KeyframeWidget::slotRefreshParams); 0242 connect(m_keyframeview, &KeyframeView::activateEffect, this, &KeyframeWidget::activateEffect); 0243 0244 connect(m_centerAction, &QAction::triggered, m_keyframeview, &KeyframeView::slotCenterKeyframe); 0245 connect(applyAction, &QAction::triggered, this, [this]() { 0246 QMultiMap<QPersistentModelIndex, QString> paramList; 0247 QList<QPersistentModelIndex> rectParams; 0248 for (const auto &w : m_parameters) { 0249 auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value<ParamType>(); 0250 if (type == ParamType::AnimatedRect) { 0251 if (m_model->data(w.first, AssetParameterModel::OpacityRole).toBool()) { 0252 paramList.insert(w.first, i18n("Opacity")); 0253 } 0254 paramList.insert(w.first, i18n("Height")); 0255 paramList.insert(w.first, i18n("Width")); 0256 paramList.insert(w.first, i18n("Y position")); 0257 paramList.insert(w.first, i18n("X position")); 0258 rectParams << w.first; 0259 } else { 0260 paramList.insert(w.first, m_model->data(w.first, Qt::DisplayRole).toString()); 0261 } 0262 } 0263 if (paramList.count() == 0) { 0264 qDebug() << "=== No parameter to copy, aborting"; 0265 return; 0266 } 0267 if (paramList.count() == 1) { 0268 m_keyframeview->copyCurrentValue(m_keyframes->getIndexAtRow(0), QString()); 0269 return; 0270 } 0271 // More than one param 0272 QDialog d(this); 0273 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0274 auto *l = new QVBoxLayout; 0275 d.setLayout(l); 0276 l->addWidget(new QLabel(i18n("Select parameters to copy"), &d)); 0277 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0278 QMapIterator<QPersistentModelIndex, QString> i(paramList); 0279 #else 0280 QMultiMapIterator<QPersistentModelIndex, QString> i(paramList); 0281 #endif 0282 while (i.hasNext()) { 0283 i.next(); 0284 auto *cb = new QCheckBox(i.value(), this); 0285 cb->setProperty("index", i.key()); 0286 l->addWidget(cb); 0287 } 0288 l->addWidget(buttonBox); 0289 d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject); 0290 d.connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept); 0291 if (d.exec() != QDialog::Accepted) { 0292 return; 0293 } 0294 paramList.clear(); 0295 QList<QCheckBox *> cbs = d.findChildren<QCheckBox *>(); 0296 QMap<QPersistentModelIndex, QStringList> params; 0297 for (auto c : qAsConst(cbs)) { 0298 // qDebug()<<"=== FOUND CBS: "<<KLocalizedString::removeAcceleratorMarker(c->text()); 0299 if (c->isChecked()) { 0300 QPersistentModelIndex ix = c->property("index").toModelIndex(); 0301 if (rectParams.contains(ix)) { 0302 // Check param name 0303 QString cbName = KLocalizedString::removeAcceleratorMarker(c->text()); 0304 if (cbName == i18n("Opacity")) { 0305 if (params.contains(ix)) { 0306 params[ix] << QStringLiteral("spinO"); 0307 } else { 0308 params.insert(ix, {QStringLiteral("spinO")}); 0309 } 0310 } else if (cbName == i18n("Height")) { 0311 if (params.contains(ix)) { 0312 params[ix] << QStringLiteral("spinH"); 0313 } else { 0314 params.insert(ix, {QStringLiteral("spinH")}); 0315 } 0316 } else if (cbName == i18n("Width")) { 0317 if (params.contains(ix)) { 0318 params[ix] << QStringLiteral("spinW"); 0319 } else { 0320 params.insert(ix, {QStringLiteral("spinW")}); 0321 } 0322 } else if (cbName == i18n("X position")) { 0323 if (params.contains(ix)) { 0324 params[ix] << QStringLiteral("spinX"); 0325 } else { 0326 params.insert(ix, {QStringLiteral("spinX")}); 0327 } 0328 } else if (cbName == i18n("Y position")) { 0329 if (params.contains(ix)) { 0330 params[ix] << QStringLiteral("spinY"); 0331 } else { 0332 params.insert(ix, {QStringLiteral("spinY")}); 0333 } 0334 } 0335 if (!params.contains(ix)) { 0336 params.insert(ix, {}); 0337 } 0338 } else { 0339 params.insert(ix, {}); 0340 } 0341 } 0342 } 0343 QMapIterator<QPersistentModelIndex, QStringList> p(params); 0344 while (p.hasNext()) { 0345 p.next(); 0346 m_keyframeview->copyCurrentValue(p.key(), p.value().join(QLatin1Char(' '))); 0347 } 0348 return; 0349 }); 0350 // m_baseHeight = m_keyframeview->height() + m_selectType->defaultWidget()->sizeHint().height(); 0351 QMargins mrg = m_lay->contentsMargins(); 0352 m_baseHeight = m_keyframeview->height() + m_toolbar->sizeHint().height(); 0353 m_addedHeight = mrg.top() + mrg.bottom(); 0354 setFixedHeight(m_baseHeight + m_addedHeight); 0355 addParameter(index); 0356 } 0357 0358 KeyframeWidget::~KeyframeWidget() 0359 { 0360 delete m_keyframeview; 0361 delete m_time; 0362 } 0363 0364 void KeyframeWidget::disconnectEffectStack() 0365 { 0366 Monitor *monitor = pCore->getMonitor(m_model->monitorId); 0367 disconnect(monitor, &Monitor::seekPosition, this, &KeyframeWidget::monitorSeek); 0368 } 0369 0370 void KeyframeWidget::monitorSeek(int pos) 0371 { 0372 int in = 0; 0373 int out = 0; 0374 bool canHaveZone = m_model->getOwnerId().type == KdenliveObjectType::Master || m_model->getOwnerId().type == KdenliveObjectType::TimelineTrack; 0375 if (canHaveZone) { 0376 bool ok = false; 0377 in = m_model->data(m_index, AssetParameterModel::InRole).toInt(&ok); 0378 out = m_model->data(m_index, AssetParameterModel::OutRole).toInt(&ok); 0379 Q_ASSERT(ok); 0380 } 0381 if (in == 0 && out == 0) { 0382 in = pCore->getItemPosition(m_model->getOwnerId()); 0383 out = in + pCore->getItemDuration(m_model->getOwnerId()); 0384 } 0385 bool isInRange = pos >= in && pos < out; 0386 connectMonitor(isInRange && m_model->isActive()); 0387 m_addDeleteAction->setEnabled(isInRange && pos > in); 0388 int framePos = qBound(in, pos, out) - in; 0389 if (isInRange && framePos != m_time->getValue()) { 0390 slotSetPosition(framePos, false); 0391 } 0392 } 0393 0394 void KeyframeWidget::slotEditKeyframeType(QAction *action) 0395 { 0396 int type = action->data().toInt(); 0397 m_keyframeview->slotEditType(type, m_index); 0398 m_selectType->setIcon(action->icon()); 0399 Q_EMIT activateEffect(); 0400 } 0401 0402 void KeyframeWidget::slotRefreshParams() 0403 { 0404 int pos = getPosition(); 0405 KeyframeType keyType = m_keyframes->keyframeType(GenTime(pos, pCore->getCurrentFps())); 0406 int i = 0; 0407 while (auto ac = m_selectType->action(i)) { 0408 if (ac->data().toInt() == int(keyType)) { 0409 m_selectType->setCurrentItem(i); 0410 m_selectType->setIcon(ac->icon()); 0411 break; 0412 } 0413 i++; 0414 } 0415 for (const auto &w : m_parameters) { 0416 auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value<ParamType>(); 0417 if (type == ParamType::KeyframeParam) { 0418 (static_cast<DoubleWidget *>(w.second))->setValue(m_keyframes->getInterpolatedValue(pos, w.first).toDouble()); 0419 } else if (type == ParamType::AnimatedRect) { 0420 const QString val = m_keyframes->getInterpolatedValue(pos, w.first).toString(); 0421 const QStringList vals = val.split(QLatin1Char(' ')); 0422 QRect rect; 0423 double opacity = -1; 0424 if (vals.count() >= 4) { 0425 rect = QRect(vals.at(0).toInt(), vals.at(1).toInt(), vals.at(2).toInt(), vals.at(3).toInt()); 0426 if (vals.count() > 4) { 0427 opacity = vals.at(4).toDouble(); 0428 } 0429 } 0430 (static_cast<GeometryWidget *>(w.second))->setValue(rect, opacity); 0431 } else if (type == ParamType::ColorWheel) { 0432 (static_cast<LumaLiftGainParam *>(w.second)->slotRefresh(pos)); 0433 } else if (type == ParamType::Color) { 0434 const QString value = m_keyframes->getInterpolatedValue(pos, w.first).toString(); 0435 (static_cast<ChooseColorWidget *>(w.second)->slotColorModified(QColorUtils::stringToColor(value))); 0436 } 0437 } 0438 if (m_monitorHelper && m_model->isActive()) { 0439 m_monitorHelper->refreshParams(pos); 0440 return; 0441 } 0442 } 0443 void KeyframeWidget::slotSetPosition(int pos, bool update) 0444 { 0445 bool canHaveZone = m_model->getOwnerId().type == KdenliveObjectType::Master || m_model->getOwnerId().type == KdenliveObjectType::TimelineTrack; 0446 int offset = 0; 0447 if (pos < 0) { 0448 if (canHaveZone) { 0449 offset = m_model->data(m_index, AssetParameterModel::InRole).toInt(); 0450 } 0451 pos = m_time->getValue(); 0452 } else { 0453 m_time->setValue(pos); 0454 } 0455 m_keyframeview->slotSetPosition(pos, true); 0456 m_addDeleteAction->setEnabled(pos > 0); 0457 slotRefreshParams(); 0458 0459 if (update) { 0460 Q_EMIT seekToPos(pos + offset); 0461 } 0462 } 0463 0464 int KeyframeWidget::getPosition() const 0465 { 0466 return m_time->getValue() + pCore->getItemIn(m_model->getOwnerId()); 0467 } 0468 0469 void KeyframeWidget::slotAtKeyframe(bool atKeyframe, bool singleKeyframe) 0470 { 0471 m_addDeleteAction->setActive(!atKeyframe); 0472 m_centerAction->setEnabled(!atKeyframe); 0473 Q_EMIT updateEffectKeyframe(atKeyframe || singleKeyframe); 0474 m_selectType->setEnabled(atKeyframe || singleKeyframe); 0475 for (const auto &w : m_parameters) { 0476 w.second->setEnabled(atKeyframe || singleKeyframe); 0477 } 0478 } 0479 0480 void KeyframeWidget::slotRefresh() 0481 { 0482 // update duration 0483 bool ok = false; 0484 int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); 0485 Q_ASSERT(ok); 0486 int in = m_model->data(m_index, AssetParameterModel::InRole).toInt(&ok); 0487 Q_ASSERT(ok); 0488 int out = in + duration; 0489 0490 m_keyframeview->setDuration(duration); 0491 m_time->setRange(0, duration - 1); 0492 if (m_model->monitorId == Kdenlive::ProjectMonitor) { 0493 monitorSeek(pCore->getMonitorPosition()); 0494 } else { 0495 int pos = m_time->getValue(); 0496 bool isInRange = pos >= in && pos < out; 0497 connectMonitor(isInRange && m_model->isActive()); 0498 m_addDeleteAction->setEnabled(isInRange && pos > in); 0499 int framePos = qBound(in, pos, out) - in; 0500 if (isInRange && framePos != m_time->getValue()) { 0501 slotSetPosition(framePos, false); 0502 } 0503 } 0504 slotRefreshParams(); 0505 } 0506 0507 void KeyframeWidget::resetKeyframes() 0508 { 0509 // update duration 0510 bool ok = false; 0511 int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); 0512 Q_ASSERT(ok); 0513 m_model->data(m_index, AssetParameterModel::InRole).toInt(&ok); 0514 Q_ASSERT(ok); 0515 // reset keyframes 0516 m_keyframes->refresh(); 0517 // m_model->dataChanged(QModelIndex(), QModelIndex()); 0518 m_keyframeview->setDuration(duration); 0519 m_time->setRange(0, duration - 1); 0520 slotRefreshParams(); 0521 } 0522 0523 void KeyframeWidget::addParameter(const QPersistentModelIndex &index) 0524 { 0525 // Retrieve parameters from the model 0526 QString name = m_model->data(index, Qt::DisplayRole).toString(); 0527 QString comment = m_model->data(index, AssetParameterModel::CommentRole).toString(); 0528 QString suffix = m_model->data(index, AssetParameterModel::SuffixRole).toString(); 0529 0530 auto type = m_model->data(index, AssetParameterModel::TypeRole).value<ParamType>(); 0531 // Construct object 0532 QWidget *labelWidget = nullptr; 0533 QWidget *paramWidget = nullptr; 0534 if (type == ParamType::AnimatedRect) { 0535 m_neededScene = MonitorSceneType::MonitorSceneGeometry; 0536 int inPos = m_model->data(index, AssetParameterModel::ParentInRole).toInt(); 0537 QPair<int, int> range(inPos, inPos + m_model->data(index, AssetParameterModel::ParentDurationRole).toInt()); 0538 const QString value = m_keyframes->getInterpolatedValue(getPosition(), index).toString(); 0539 m_monitorHelper = new KeyframeMonitorHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this); 0540 QRect rect; 0541 double opacity = 0; 0542 QStringList vals = value.split(QLatin1Char(' ')); 0543 if (vals.count() > 3) { 0544 rect = QRect(vals.at(0).toInt(), vals.at(1).toInt(), vals.at(2).toInt(), vals.at(3).toInt()); 0545 if (vals.count() > 4) { 0546 opacity = vals.at(4).toDouble(); 0547 } 0548 } 0549 // qtblend uses an opacity value in the (0-1) range, while older geometry effects use (0-100) 0550 GeometryWidget *geomWidget = new GeometryWidget(pCore->getMonitor(m_model->monitorId), range, rect, opacity, m_sourceFrameSize, false, 0551 m_model->data(m_index, AssetParameterModel::OpacityRole).toBool(), this); 0552 connect(geomWidget, &GeometryWidget::valueChanged, this, [this, index](const QString &v) { 0553 Q_EMIT activateEffect(); 0554 m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index); 0555 }); 0556 connect(geomWidget, &GeometryWidget::updateMonitorGeometry, this, [this](const QRect r) { 0557 if (m_model->isActive()) { 0558 pCore->getMonitor(m_model->monitorId)->setUpEffectGeometry(r); 0559 } 0560 }); 0561 paramWidget = geomWidget; 0562 } else if (type == ParamType::ColorWheel) { 0563 auto colorWheelWidget = new LumaLiftGainParam(m_model, index, this); 0564 connect(colorWheelWidget, &LumaLiftGainParam::valuesChanged, this, 0565 [this, index](const QList<QModelIndex> &indexes, const QStringList &sourceList, const QStringList &list, bool createUndo) { 0566 Q_EMIT activateEffect(); 0567 if (createUndo) { 0568 m_keyframes->updateMultiKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), sourceList, list, indexes); 0569 } else { 0570 // Execute without creating an undo/redo entry 0571 auto *parentCommand = new QUndoCommand(); 0572 m_keyframes->updateMultiKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), sourceList, list, indexes, parentCommand); 0573 parentCommand->redo(); 0574 delete parentCommand; 0575 } 0576 }); 0577 connect(colorWheelWidget, &LumaLiftGainParam::updateHeight, this, [&](int h) { 0578 setFixedHeight(m_baseHeight + m_addedHeight + h); 0579 Q_EMIT updateHeight(); 0580 }); 0581 paramWidget = colorWheelWidget; 0582 } else if (type == ParamType::Roto_spline) { 0583 m_monitorHelper = new RotoHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this); 0584 m_neededScene = MonitorSceneType::MonitorSceneRoto; 0585 } else if (type == ParamType::Color) { 0586 QString value = m_keyframes->getInterpolatedValue(getPosition(), index).toString(); 0587 bool alphaEnabled = m_model->data(index, AssetParameterModel::AlphaRole).toBool(); 0588 labelWidget = new QLabel(name, this); 0589 auto colorWidget = new ChooseColorWidget(this, QColorUtils::stringToColor(value), alphaEnabled); 0590 colorWidget->setToolTip(comment); 0591 connect(colorWidget, &ChooseColorWidget::modified, this, [this, index, alphaEnabled](const QColor &color) { 0592 Q_EMIT activateEffect(); 0593 m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(QColorUtils::colorToString(color, alphaEnabled)), index); 0594 }); 0595 paramWidget = colorWidget; 0596 0597 } else { 0598 if (m_model->getAssetId() == QLatin1String("frei0r.c0rners")) { 0599 if (m_neededScene == MonitorSceneDefault && !m_monitorHelper) { 0600 m_neededScene = MonitorSceneType::MonitorSceneCorners; 0601 m_monitorHelper = new CornersHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this); 0602 connect(this, &KeyframeWidget::addIndex, m_monitorHelper, &CornersHelper::addIndex); 0603 } else { 0604 if (type == ParamType::KeyframeParam) { 0605 int paramName = m_model->data(index, AssetParameterModel::NameRole).toInt(); 0606 if (paramName < 8) { 0607 Q_EMIT addIndex(index); 0608 } 0609 } 0610 } 0611 } 0612 if (m_model->getAssetId().contains(QLatin1String("frei0r.alphaspot"))) { 0613 if (m_neededScene == MonitorSceneDefault && !m_monitorHelper) { 0614 m_neededScene = MonitorSceneType::MonitorSceneGeometry; 0615 m_monitorHelper = new RectHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this); 0616 connect(this, &KeyframeWidget::addIndex, m_monitorHelper, &RectHelper::addIndex); 0617 } else { 0618 if (type == ParamType::KeyframeParam) { 0619 QString paramName = m_model->data(index, AssetParameterModel::NameRole).toString(); 0620 if (paramName.contains(QLatin1String("Position X")) || paramName.contains(QLatin1String("Position Y")) || 0621 paramName.contains(QLatin1String("Size X")) || paramName.contains(QLatin1String("Size Y"))) { 0622 Q_EMIT addIndex(index); 0623 } 0624 } 0625 } 0626 } 0627 double value = m_keyframes->getInterpolatedValue(getPosition(), index).toDouble(); 0628 double min = m_model->data(index, AssetParameterModel::MinRole).toDouble(); 0629 double max = m_model->data(index, AssetParameterModel::MaxRole).toDouble(); 0630 double defaultValue = m_model->data(index, AssetParameterModel::DefaultRole).toDouble(); 0631 int decimals = m_model->data(index, AssetParameterModel::DecimalsRole).toInt(); 0632 double factor = m_model->data(index, AssetParameterModel::FactorRole).toDouble(); 0633 factor = qFuzzyIsNull(factor) ? 1 : factor; 0634 auto doubleWidget = new DoubleWidget(name, value, min, max, factor, defaultValue, comment, -1, suffix, decimals, 0635 m_model->data(index, AssetParameterModel::OddRole).toBool(), this); 0636 connect(doubleWidget, &DoubleWidget::valueChanged, this, [this, index](double v) { 0637 Q_EMIT activateEffect(); 0638 m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index); 0639 }); 0640 doubleWidget->setDragObjectName(QString::number(index.row())); 0641 paramWidget = doubleWidget; 0642 } 0643 if (paramWidget) { 0644 m_parameters[index] = paramWidget; 0645 if (labelWidget) { 0646 auto *hbox = new QHBoxLayout(this); 0647 hbox->setContentsMargins(0, 0, 0, 0); 0648 hbox->setSpacing(0); 0649 hbox->addWidget(labelWidget, 1); 0650 hbox->addWidget(paramWidget, 1); 0651 m_lay->addLayout(hbox); 0652 } else { 0653 m_lay->addWidget(paramWidget); 0654 } 0655 m_addedHeight += paramWidget->minimumHeight(); 0656 setFixedHeight(m_baseHeight + m_addedHeight); 0657 } 0658 } 0659 0660 void KeyframeWidget::slotInitMonitor(bool active) 0661 { 0662 connectMonitor(active); 0663 Monitor *monitor = pCore->getMonitor(m_model->monitorId); 0664 if (m_keyframeview) { 0665 m_keyframeview->initKeyframePos(); 0666 connect(monitor, &Monitor::updateScene, m_keyframeview, &KeyframeView::slotModelChanged, Qt::UniqueConnection); 0667 } 0668 } 0669 0670 void KeyframeWidget::connectMonitor(bool active) 0671 { 0672 if (m_monitorHelper) { 0673 if (m_model->isActive()) { 0674 connect(m_monitorHelper, &KeyframeMonitorHelper::updateKeyframeData, this, &KeyframeWidget::slotUpdateKeyframesFromMonitor, Qt::UniqueConnection); 0675 if (m_monitorHelper->connectMonitor(active)) { 0676 slotRefreshParams(); 0677 } 0678 } else { 0679 m_monitorHelper->connectMonitor(false); 0680 disconnect(m_monitorHelper, &KeyframeMonitorHelper::updateKeyframeData, this, &KeyframeWidget::slotUpdateKeyframesFromMonitor); 0681 } 0682 } 0683 Monitor *monitor = pCore->getMonitor(m_model->monitorId); 0684 if (active) { 0685 connect(monitor, &Monitor::seekToNextKeyframe, m_keyframeview, &KeyframeView::slotGoToNext, Qt::UniqueConnection); 0686 connect(monitor, &Monitor::seekToPreviousKeyframe, m_keyframeview, &KeyframeView::slotGoToPrev, Qt::UniqueConnection); 0687 connect(monitor, &Monitor::addRemoveKeyframe, m_keyframeview, &KeyframeView::slotAddRemove, Qt::UniqueConnection); 0688 connect(this, &KeyframeWidget::updateEffectKeyframe, monitor, &Monitor::setEffectKeyframe, Qt::DirectConnection); 0689 connect(monitor, &Monitor::seekToKeyframe, this, &KeyframeWidget::slotSeekToKeyframe, Qt::UniqueConnection); 0690 } else { 0691 disconnect(monitor, &Monitor::seekToNextKeyframe, m_keyframeview, &KeyframeView::slotGoToNext); 0692 disconnect(monitor, &Monitor::seekToPreviousKeyframe, m_keyframeview, &KeyframeView::slotGoToPrev); 0693 disconnect(monitor, &Monitor::addRemoveKeyframe, m_keyframeview, &KeyframeView::slotAddRemove); 0694 disconnect(this, &KeyframeWidget::updateEffectKeyframe, monitor, &Monitor::setEffectKeyframe); 0695 disconnect(monitor, &Monitor::seekToKeyframe, this, &KeyframeWidget::slotSeekToKeyframe); 0696 } 0697 for (const auto &w : m_parameters) { 0698 auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value<ParamType>(); 0699 if (type == ParamType::AnimatedRect) { 0700 (static_cast<GeometryWidget *>(w.second))->connectMonitor(active); 0701 break; 0702 } 0703 } 0704 } 0705 0706 void KeyframeWidget::slotUpdateKeyframesFromMonitor(const QPersistentModelIndex &index, const QVariant &res) 0707 { 0708 Q_EMIT activateEffect(); 0709 if (m_keyframes->isEmpty()) { 0710 GenTime pos(pCore->getItemIn(m_model->getOwnerId()) + m_time->getValue(), pCore->getCurrentFps()); 0711 if (m_time->getValue() > 0) { 0712 // First add keyframe at start of the clip 0713 GenTime pos0(pCore->getItemIn(m_model->getOwnerId()), pCore->getCurrentFps()); 0714 m_keyframes->addKeyframe(pos0, KeyframeType::Linear); 0715 m_keyframes->updateKeyframe(pos0, res, index); 0716 // For rotoscoping, don't add a second keyframe at cursor pos 0717 auto type = m_model->data(index, AssetParameterModel::TypeRole).value<ParamType>(); 0718 if (type == ParamType::Roto_spline) { 0719 return; 0720 } 0721 } 0722 // Next add keyframe at playhead position 0723 m_keyframes->addKeyframe(pos, KeyframeType::Linear); 0724 m_keyframes->updateKeyframe(pos, res, index); 0725 } else if (m_keyframes->hasKeyframe(getPosition()) || m_keyframes->singleKeyframe()) { 0726 GenTime pos(getPosition(), pCore->getCurrentFps()); 0727 // Auto add keyframe only if there already is more than 1 keyframe 0728 if (!m_keyframes->singleKeyframe() && KdenliveSettings::autoKeyframe() && m_neededScene == MonitorSceneType::MonitorSceneRoto) { 0729 m_keyframes->addKeyframe(pos, KeyframeType::Linear); 0730 } 0731 m_keyframes->updateKeyframe(pos, res, index); 0732 } else { 0733 qDebug() << "==== NO KFR AT: " << getPosition(); 0734 } 0735 } 0736 0737 MonitorSceneType KeyframeWidget::requiredScene() const 0738 { 0739 qDebug() << "// // // RESULTING REQUIRED SCENE: " << m_neededScene; 0740 return m_neededScene; 0741 } 0742 0743 bool KeyframeWidget::keyframesVisible() const 0744 { 0745 return m_keyframeview->isVisible(); 0746 } 0747 0748 void KeyframeWidget::showKeyframes(bool enable) 0749 { 0750 if (enable && m_toolbar->isVisible()) { 0751 return; 0752 } 0753 m_toolbar->setVisible(enable); 0754 m_keyframeview->setVisible(enable); 0755 m_time->setVisible(enable); 0756 setFixedHeight(m_addedHeight + (enable ? m_baseHeight : 0)); 0757 } 0758 0759 void KeyframeWidget::slotCopyKeyframes() 0760 { 0761 QJsonDocument effectDoc = m_model->toJson({}, false); 0762 if (effectDoc.isEmpty()) { 0763 return; 0764 } 0765 QClipboard *clipboard = QApplication::clipboard(); 0766 clipboard->setText(QString(effectDoc.toJson())); 0767 pCore->displayMessage(i18n("Keyframes copied"), InformationMessage); 0768 } 0769 0770 void KeyframeWidget::slotPasteKeyframeFromClipBoard() 0771 { 0772 QClipboard *clipboard = QApplication::clipboard(); 0773 QString values = clipboard->text(); 0774 auto json = QJsonDocument::fromJson(values.toUtf8()); 0775 Fun undo = []() { return true; }; 0776 Fun redo = []() { return true; }; 0777 if (!json.isArray()) { 0778 pCore->displayMessage(i18n("No valid keyframe data in clipboard"), InformationMessage); 0779 return; 0780 } 0781 auto list = json.array(); 0782 QMap<QString, QMap<int, QVariant>> storedValues; 0783 for (const auto &entry : qAsConst(list)) { 0784 if (!entry.isObject()) { 0785 qDebug() << "Warning : Skipping invalid marker data"; 0786 continue; 0787 } 0788 auto entryObj = entry.toObject(); 0789 if (!entryObj.contains(QLatin1String("name"))) { 0790 qDebug() << "Warning : Skipping invalid marker data (does not contain name)"; 0791 continue; 0792 } 0793 0794 ParamType kfrType = entryObj[QLatin1String("type")].toVariant().value<ParamType>(); 0795 if (m_model->isAnimated(kfrType)) { 0796 QMap<int, QVariant> values; 0797 if (kfrType == ParamType::Roto_spline) { 0798 auto value = entryObj.value(QLatin1String("value")); 0799 if (value.isObject()) { 0800 QJsonObject obj = value.toObject(); 0801 QStringList keys = obj.keys(); 0802 for (auto &k : keys) { 0803 values.insert(k.toInt(), obj.value(k)); 0804 } 0805 } else if (value.isArray()) { 0806 auto list = value.toArray(); 0807 for (const auto &entry : qAsConst(list)) { 0808 if (!entry.isObject()) { 0809 qDebug() << "Warning : Skipping invalid category data"; 0810 continue; 0811 } 0812 QJsonObject obj = entry.toObject(); 0813 QStringList keys = obj.keys(); 0814 for (auto &k : keys) { 0815 values.insert(k.toInt(), obj.value(k)); 0816 } 0817 } 0818 } else { 0819 pCore->displayMessage(i18n("No valid keyframe data in clipboard"), InformationMessage); 0820 qDebug() << "::: Invalid ROTO VALUE, ABORTING PASTE\n" << value; 0821 return; 0822 } 0823 } else { 0824 const QString value = entryObj.value(QLatin1String("value")).toString(); 0825 if (value.isEmpty()) { 0826 pCore->displayMessage(i18n("No valid keyframe data in clipboard"), InformationMessage); 0827 qDebug() << "::: Invalid KFR VALUE, ABORTING PASTE\n" << value; 0828 return; 0829 } 0830 const QStringList stringVals = value.split(QLatin1Char(';'), Qt::SkipEmptyParts); 0831 for (auto &val : stringVals) { 0832 int position = m_model->time_to_frames(val.section(QLatin1Char('='), 0, 0)); 0833 values.insert(position, val.section(QLatin1Char('='), 1)); 0834 } 0835 } 0836 storedValues.insert(entryObj[QLatin1String("name")].toString(), values); 0837 } else { 0838 const QString value = entryObj.value(QLatin1String("value")).toString(); 0839 QMap<int, QVariant> values; 0840 values.insert(0, value); 0841 storedValues.insert(entryObj[QLatin1String("name")].toString(), values); 0842 } 0843 } 0844 int destPos = getPosition(); 0845 0846 std::vector<QPersistentModelIndex> indexes = m_keyframes->getIndexes(); 0847 for (const auto &ix : indexes) { 0848 auto paramName = m_model->data(ix, AssetParameterModel::NameRole).toString(); 0849 if (storedValues.contains(paramName)) { 0850 KeyframeModel *km = m_keyframes->getKeyModel(ix); 0851 QMap<int, QVariant> values = storedValues.value(paramName); 0852 int offset = values.keys().first(); 0853 QMapIterator<int, QVariant> i(values); 0854 while (i.hasNext()) { 0855 i.next(); 0856 km->addKeyframe(GenTime(destPos + i.key() - offset, pCore->getCurrentFps()), KeyframeType::Linear, i.value(), true, undo, redo); 0857 } 0858 } else { 0859 qDebug() << "::: NOT FOUND PARAM: " << paramName << " in list: " << storedValues.keys(); 0860 } 0861 } 0862 pCore->pushUndo(undo, redo, i18n("Paste keyframe")); 0863 } 0864 0865 void KeyframeWidget::slotCopySelectedKeyframes() 0866 { 0867 const QVector<int> results = m_keyframeview->selectedKeyframesIndexes(); 0868 QJsonDocument effectDoc = m_model->toJson(results, false); 0869 if (effectDoc.isEmpty()) { 0870 pCore->displayMessage(i18n("Cannot copy current parameter values"), InformationMessage); 0871 return; 0872 } 0873 QClipboard *clipboard = QApplication::clipboard(); 0874 clipboard->setText(QString(effectDoc.toJson())); 0875 pCore->displayMessage(i18n("Current values copied"), InformationMessage); 0876 } 0877 0878 void KeyframeWidget::slotCopyValueAtCursorPos() 0879 { 0880 QJsonDocument effectDoc = m_model->valueAsJson(getPosition(), false); 0881 if (effectDoc.isEmpty()) { 0882 return; 0883 } 0884 QClipboard *clipboard = QApplication::clipboard(); 0885 clipboard->setText(QString(effectDoc.toJson())); 0886 pCore->displayMessage(i18n("Current values copied"), InformationMessage); 0887 } 0888 0889 void KeyframeWidget::slotImportKeyframes() 0890 { 0891 QClipboard *clipboard = QApplication::clipboard(); 0892 QString values = clipboard->text(); 0893 QList<QPersistentModelIndex> indexes; 0894 for (const auto &w : m_parameters) { 0895 indexes << w.first; 0896 } 0897 if (m_neededScene == MonitorSceneRoto) { 0898 indexes << m_monitorHelper->getIndexes(); 0899 } 0900 QPointer<KeyframeImport> import = new KeyframeImport(values, m_model, indexes, m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(), 0901 m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(), this); 0902 import->show(); 0903 connect(import, &KeyframeImport::updateQmlView, this, &KeyframeWidget::slotRefreshParams); 0904 } 0905 0906 void KeyframeWidget::slotRemoveNextKeyframes() 0907 { 0908 int pos = m_time->getValue() + m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(); 0909 m_keyframes->removeNextKeyframes(GenTime(pos, pCore->getCurrentFps())); 0910 } 0911 0912 void KeyframeWidget::slotSeekToKeyframe(int ix) 0913 { 0914 int pos = m_keyframes->getPosAtIndex(ix).frames(pCore->getCurrentFps()); 0915 slotSetPosition(pos, true); 0916 } 0917 0918 void KeyframeWidget::sendStandardCommand(int command) 0919 { 0920 switch (command) { 0921 case KStandardAction::Copy: 0922 m_copyAction->trigger(); 0923 break; 0924 case KStandardAction::Paste: 0925 m_pasteAction->trigger(); 0926 break; 0927 default: 0928 qDebug() << ":::: UNKNOWN COMMAND: " << command; 0929 break; 0930 } 0931 }