File indexing completed on 2024-05-19 04:54:25
0001 /* 0002 SPDX-FileCopyrightText: 2017 Jean-Baptiste Mardelle <jb@kdenlive.org> 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 #include "collapsibleeffectview.hpp" 0007 #include "assets/keyframes/view/keyframeview.hpp" 0008 #include "assets/view/assetparameterview.hpp" 0009 #include "assets/view/widgets/colorwheel.h" 0010 #include "assets/view/widgets/keyframewidget.hpp" 0011 #include "core.h" 0012 #include "dialogs/clipcreationdialog.h" 0013 #include "effects/effectsrepository.hpp" 0014 #include "effects/effectstack/model/effectitemmodel.hpp" 0015 #include "kdenlivesettings.h" 0016 #include "monitor/monitor.h" 0017 0018 #include "kdenlive_debug.h" 0019 #include <QDialog> 0020 #include <QFileDialog> 0021 #include <QFontDatabase> 0022 #include <QFormLayout> 0023 #include <QInputDialog> 0024 #include <QLabel> 0025 #include <QMenu> 0026 #include <QMimeData> 0027 #include <QPointer> 0028 #include <QProgressBar> 0029 #include <QSpinBox> 0030 #include <QStandardPaths> 0031 #include <QTextEdit> 0032 #include <QVBoxLayout> 0033 #include <QWheelEvent> 0034 0035 #include "utils/KMessageBox_KdenliveCompat.h" 0036 #include <KDualAction> 0037 #include <KLocalizedString> 0038 #include <KMessageBox> 0039 #include <KRecentDirs> 0040 #include <KSqueezedTextLabel> 0041 #include <QComboBox> 0042 0043 CollapsibleEffectView::CollapsibleEffectView(const QString &effectName, const std::shared_ptr<EffectItemModel> &effectModel, QSize frameSize, QWidget *parent) 0044 : AbstractCollapsibleWidget(parent) 0045 , m_view(nullptr) 0046 , m_model(effectModel) 0047 , m_blockWheel(false) 0048 , m_dragging(false) 0049 { 0050 QString effectId = effectModel->getAssetId(); 0051 buttonUp->setIcon(QIcon::fromTheme(QStringLiteral("selection-raise"))); 0052 buttonUp->setToolTip(i18n("Move effect up")); 0053 buttonUp->setWhatsThis(xi18nc( 0054 "@info:whatsthis", "Moves the effect above the one right above it. Effects are handled sequentially from top to bottom so sequence is important.")); 0055 buttonDown->setIcon(QIcon::fromTheme(QStringLiteral("selection-lower"))); 0056 buttonDown->setToolTip(i18n("Move effect down")); 0057 buttonDown->setWhatsThis(xi18nc( 0058 "@info:whatsthis", "Moves the effect below the one right below it. Effects are handled sequentially from top to bottom so sequence is important.")); 0059 buttonDel->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); 0060 buttonDel->setToolTip(i18n("Delete effect")); 0061 buttonDel->setWhatsThis(xi18nc("@info:whatsthis", "Deletes the effect from the effect stack.")); 0062 0063 if (effectId == QLatin1String("speed")) { 0064 // Speed effect is a "pseudo" effect, cannot be moved 0065 buttonUp->setVisible(false); 0066 buttonDown->setVisible(false); 0067 m_isMovable = false; 0068 setAcceptDrops(false); 0069 } else { 0070 setAcceptDrops(true); 0071 } 0072 0073 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); 0074 m_collapse = new KDualAction(i18n("Collapse Effect"), i18n("Expand Effect"), this); 0075 m_collapse->setActiveIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); 0076 collapseButton->setDefaultAction(m_collapse); 0077 m_collapse->setActive(m_model->isCollapsed()); 0078 connect(m_collapse, &KDualAction::activeChanged, this, &CollapsibleEffectView::slotSwitch); 0079 0080 if (effectModel->rowCount() == 0) { 0081 // Effect has no parameter 0082 m_collapse->setInactiveIcon(QIcon::fromTheme(QStringLiteral("tools-wizard"))); 0083 collapseButton->setEnabled(false); 0084 } else { 0085 m_collapse->setInactiveIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); 0086 } 0087 0088 auto *l = static_cast<QHBoxLayout *>(frame->layout()); 0089 title = new KSqueezedTextLabel(this); 0090 title->setToolTip(effectName); 0091 title->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); 0092 l->insertWidget(1, title); 0093 0094 keyframesButton->setIcon(QIcon::fromTheme(QStringLiteral("keyframe"))); 0095 keyframesButton->setCheckable(true); 0096 keyframesButton->setToolTip(i18n("Enable Keyframes")); 0097 0098 m_keyframesButton = new KDualAction(i18n("Hide Keyframes"), i18n("Show Keyframes"), this); 0099 m_keyframesButton->setWhatsThis(xi18nc("@info:whatsthis", "Turns the display of the keyframe ruler on.")); 0100 m_keyframesButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("keyframe-disable"))); 0101 m_keyframesButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("keyframe"))); 0102 keyframesButton->setDefaultAction(m_keyframesButton); 0103 connect(m_keyframesButton, &KDualAction::activeChangedByUser, this, &CollapsibleEffectView::slotHideKeyframes); 0104 connect(m_model.get(), &AssetParameterModel::hideKeyframesChange, this, &CollapsibleEffectView::enableHideKeyframes); 0105 0106 // Enable button 0107 m_enabledButton = new KDualAction(i18n("Disable Effect"), i18n("Enable Effect"), this); 0108 m_enabledButton->setWhatsThis(xi18nc("@info:whatsthis", "Disables the effect. Useful to compare before and after settings.")); 0109 m_enabledButton->setWhatsThis(xi18nc("@info:whatsthis", "Enables the effect. Useful to compare before and after settings.")); 0110 m_enabledButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("hint"))); 0111 m_enabledButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("visibility"))); 0112 enabledButton->setDefaultAction(m_enabledButton); 0113 connect(m_model.get(), &AssetParameterModel::enabledChange, this, &CollapsibleEffectView::enableView); 0114 connect(m_model.get(), &AssetParameterModel::showEffectZone, this, [=](ObjectId id, QPair<int, int> inOut, bool checked) { 0115 m_inOutButton->setChecked(checked); 0116 zoneFrame->setFixedHeight(checked ? frame->height() : 0); 0117 slotSwitch(m_collapse->isActive()); 0118 if (checked) { 0119 QSignalBlocker bk(m_inPos); 0120 QSignalBlocker bk2(m_outPos); 0121 m_inPos->setValue(inOut.first); 0122 m_outPos->setValue(inOut.second); 0123 } 0124 Q_EMIT showEffectZone(id, inOut, checked); 0125 }); 0126 m_groupAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-new")), i18n("Create Group"), this); 0127 connect(m_groupAction, &QAction::triggered, this, &CollapsibleEffectView::slotCreateGroup); 0128 0129 // In /out effect button 0130 auto *layZone = new QHBoxLayout(zoneFrame); 0131 layZone->setContentsMargins(0, 0, 0, 0); 0132 layZone->setSpacing(0); 0133 QLabel *in = new QLabel(i18n("In:"), this); 0134 in->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 0135 layZone->addWidget(in); 0136 auto *setIn = new QToolButton(this); 0137 setIn->setIcon(QIcon::fromTheme(QStringLiteral("zone-in"))); 0138 setIn->setAutoRaise(true); 0139 setIn->setToolTip(i18n("Set zone in")); 0140 setIn->setWhatsThis(xi18nc("@info:whatsthis", "Sets the current frame/playhead position as start of the zone.")); 0141 layZone->addWidget(setIn); 0142 m_inPos = new TimecodeDisplay(this); 0143 layZone->addWidget(m_inPos); 0144 layZone->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, QSizePolicy::Maximum)); 0145 QLabel *out = new QLabel(i18n("Out:"), this); 0146 out->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 0147 layZone->addWidget(out); 0148 auto *setOut = new QToolButton(this); 0149 setOut->setIcon(QIcon::fromTheme(QStringLiteral("zone-out"))); 0150 setOut->setAutoRaise(true); 0151 setOut->setToolTip(i18n("Set zone out")); 0152 setOut->setWhatsThis(xi18nc("@info:whatsthis", "Sets the current frame/playhead position as end of the zone.")); 0153 layZone->addWidget(setOut); 0154 m_outPos = new TimecodeDisplay(this); 0155 layZone->addWidget(m_outPos); 0156 0157 connect(setIn, &QToolButton::clicked, this, [=]() { 0158 if (m_model->getOwnerId().type == KdenliveObjectType::BinClip) { 0159 m_outPos->setValue(pCore->getMonitor(Kdenlive::ClipMonitor)->position()); 0160 } else { 0161 m_inPos->setValue(pCore->getMonitorPosition()); 0162 } 0163 updateEffectZone(); 0164 }); 0165 connect(setOut, &QToolButton::clicked, this, [=]() { 0166 if (m_model->getOwnerId().type == KdenliveObjectType::BinClip) { 0167 m_outPos->setValue(pCore->getMonitor(Kdenlive::ClipMonitor)->position()); 0168 } else { 0169 m_outPos->setValue(pCore->getMonitorPosition()); 0170 } 0171 updateEffectZone(); 0172 }); 0173 0174 m_inOutButton = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-width")), i18n("Use effect zone"), this); 0175 m_inOutButton->setWhatsThis(xi18nc("@info:whatsthis", "Toggles the display of the effect zone.")); 0176 m_inOutButton->setCheckable(true); 0177 inOutButton->setDefaultAction(m_inOutButton); 0178 m_inOutButton->setChecked(m_model->hasForcedInOut()); 0179 if (m_inOutButton->isChecked()) { 0180 QPair<int, int> inOut = m_model->getInOut(); 0181 m_inPos->setValue(inOut.first); 0182 m_outPos->setValue(inOut.second); 0183 } else { 0184 zoneFrame->setFixedHeight(0); 0185 } 0186 inOutButton->setVisible(m_model->getOwnerId().type != KdenliveObjectType::TimelineClip); 0187 connect(m_inPos, &TimecodeDisplay::timeCodeEditingFinished, this, &CollapsibleEffectView::updateEffectZone); 0188 connect(m_outPos, &TimecodeDisplay::timeCodeEditingFinished, this, &CollapsibleEffectView::updateEffectZone); 0189 connect(m_inOutButton, &QAction::triggered, this, &CollapsibleEffectView::switchInOut); 0190 0191 title->setText(effectName); 0192 frame->setMinimumHeight(collapseButton->sizeHint().height()); 0193 0194 m_view = new AssetParameterView(this); 0195 const std::shared_ptr<AssetParameterModel> effectParamModel = std::static_pointer_cast<AssetParameterModel>(effectModel); 0196 m_view->setModel(effectParamModel, frameSize); 0197 connect(m_view, &AssetParameterView::seekToPos, this, &AbstractCollapsibleWidget::seekToPos); 0198 connect(m_view, &AssetParameterView::activateEffect, this, [this]() { 0199 if (!decoframe->property("active").toBool()) { 0200 // Activate effect if not already active 0201 Q_EMIT activateEffect(m_model->row()); 0202 } 0203 }); 0204 0205 if (effectModel->rowCount() == 0) { 0206 // Effect has no parameter 0207 m_view->setVisible(false); 0208 } 0209 0210 connect(m_view, &AssetParameterView::updateHeight, this, &CollapsibleEffectView::updateHeight); 0211 connect(this, &CollapsibleEffectView::refresh, m_view, &AssetParameterView::slotRefresh); 0212 keyframesButton->setVisible(m_view->keyframesAllowed()); 0213 auto *lay = new QVBoxLayout(widgetFrame); 0214 lay->setContentsMargins(0, 0, 0, 0); 0215 lay->setSpacing(0); 0216 lay->addWidget(m_view); 0217 0218 if (!effectParamModel->hasMoreThanOneKeyframe()) { 0219 // No keyframe or only one, allow hiding 0220 bool hideByDefault = effectParamModel->data(effectParamModel->index(0, 0), AssetParameterModel::HideKeyframesFirstRole).toBool(); 0221 if (hideByDefault && m_model->keyframesHiddenUnset()) { 0222 m_model->setKeyframesHidden(true); 0223 } 0224 } 0225 0226 if (m_model->isKeyframesHidden()) { 0227 m_view->toggleKeyframes(false); 0228 m_keyframesButton->setActive(true); 0229 } 0230 // Presets 0231 presetButton->setIcon(QIcon::fromTheme(QStringLiteral("adjustlevels"))); 0232 presetButton->setMenu(m_view->presetMenu()); 0233 presetButton->setToolTip(i18n("Presets")); 0234 presetButton->setWhatsThis(xi18nc("@info:whatsthis", "Opens a list of advanced options to manage presets for the effect.")); 0235 0236 connect(saveEffectButton, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotSaveEffect); 0237 saveEffectButton->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); 0238 saveEffectButton->setToolTip(i18n("Save effect")); 0239 0240 if (!effectModel->isEnabled()) { 0241 title->setEnabled(false); 0242 if (KdenliveSettings::disable_effect_parameters()) { 0243 widgetFrame->setEnabled(false); 0244 } 0245 m_enabledButton->setActive(true); 0246 } else { 0247 m_enabledButton->setActive(false); 0248 } 0249 0250 connect(m_enabledButton, &KDualAction::activeChangedByUser, this, &CollapsibleEffectView::slotDisable); 0251 connect(buttonUp, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotEffectUp); 0252 connect(buttonDown, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotEffectDown); 0253 connect(buttonDel, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotDeleteEffect); 0254 0255 for (QSpinBox *sp : findChildren<QSpinBox *>()) { 0256 sp->installEventFilter(this); 0257 sp->setFocusPolicy(Qt::StrongFocus); 0258 } 0259 for (QComboBox *cb : findChildren<QComboBox *>()) { 0260 cb->installEventFilter(this); 0261 cb->setFocusPolicy(Qt::StrongFocus); 0262 } 0263 for (QProgressBar *cb : findChildren<QProgressBar *>()) { 0264 cb->installEventFilter(this); 0265 cb->setFocusPolicy(Qt::StrongFocus); 0266 } 0267 for (WheelContainer *cb : findChildren<WheelContainer *>()) { 0268 cb->installEventFilter(this); 0269 cb->setFocusPolicy(Qt::StrongFocus); 0270 } 0271 for (QDoubleSpinBox *cb : findChildren<QDoubleSpinBox *>()) { 0272 cb->installEventFilter(this); 0273 cb->setFocusPolicy(Qt::StrongFocus); 0274 } 0275 QMetaObject::invokeMethod(this, "slotSwitch", Qt::QueuedConnection, Q_ARG(bool, m_model->isCollapsed())); 0276 } 0277 0278 CollapsibleEffectView::~CollapsibleEffectView() 0279 { 0280 qDebug() << "deleting collapsibleeffectview"; 0281 } 0282 0283 void CollapsibleEffectView::setWidgetHeight(qreal value) 0284 { 0285 widgetFrame->setFixedHeight(int(m_view->contentHeight() * value)); 0286 } 0287 0288 void CollapsibleEffectView::slotCreateGroup() 0289 { 0290 Q_EMIT createGroup(m_model); 0291 } 0292 0293 void CollapsibleEffectView::slotCreateRegion() 0294 { 0295 const QString dialogFilter = ClipCreationDialog::getExtensionsFilter(QStringList() << i18n("All Files") + QStringLiteral(" (*)")); 0296 QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); 0297 if (clipFolder.isEmpty()) { 0298 clipFolder = QDir::homePath(); 0299 } 0300 QPointer<QFileDialog> d = new QFileDialog(QApplication::activeWindow(), QString(), clipFolder, dialogFilter); 0301 d->setFileMode(QFileDialog::ExistingFile); 0302 if (d->exec() == QDialog::Accepted && !d->selectedUrls().isEmpty()) { 0303 KRecentDirs::add(QStringLiteral(":KdenliveClipFolder"), d->selectedUrls().first().adjusted(QUrl::RemoveFilename).toLocalFile()); 0304 Q_EMIT createRegion(effectIndex(), d->selectedUrls().first()); 0305 } 0306 delete d; 0307 } 0308 0309 void CollapsibleEffectView::slotUnGroup() 0310 { 0311 Q_EMIT unGroup(this); 0312 } 0313 0314 bool CollapsibleEffectView::eventFilter(QObject *o, QEvent *e) 0315 { 0316 if (e->type() == QEvent::Enter) { 0317 frame->setProperty("mouseover", true); 0318 frame->setStyleSheet(frame->styleSheet()); 0319 return QWidget::eventFilter(o, e); 0320 } 0321 if (e->type() == QEvent::Wheel) { 0322 auto *we = static_cast<QWheelEvent *>(e); 0323 if (!m_blockWheel || we->modifiers() != Qt::NoModifier) { 0324 return false; 0325 } 0326 if (qobject_cast<QAbstractSpinBox *>(o)) { 0327 if (m_blockWheel && !qobject_cast<QAbstractSpinBox *>(o)->hasFocus()) { 0328 return true; 0329 } 0330 return false; 0331 } 0332 if (qobject_cast<QComboBox *>(o)) { 0333 if (qobject_cast<QComboBox *>(o)->focusPolicy() == Qt::WheelFocus) { 0334 return false; 0335 } 0336 return true; 0337 } 0338 if (qobject_cast<QProgressBar *>(o)) { 0339 if (!qobject_cast<QProgressBar *>(o)->hasFocus()) { 0340 return true; 0341 } 0342 return false; 0343 } 0344 if (qobject_cast<WheelContainer *>(o)) { 0345 if (!qobject_cast<WheelContainer *>(o)->hasFocus()) { 0346 return true; 0347 } 0348 return false; 0349 } 0350 if (qobject_cast<KeyframeView *>(o)) { 0351 if (!qobject_cast<KeyframeView *>(o)->hasFocus()) { 0352 return true; 0353 } 0354 return false; 0355 } 0356 } 0357 return QWidget::eventFilter(o, e); 0358 } 0359 0360 QDomElement CollapsibleEffectView::effect() const 0361 { 0362 return m_effect; 0363 } 0364 0365 QDomElement CollapsibleEffectView::effectForSave() const 0366 { 0367 QDomElement effect = m_effect.cloneNode().toElement(); 0368 effect.removeAttribute(QStringLiteral("kdenlive_ix")); 0369 /* 0370 if (m_paramWidget) { 0371 int in = m_paramWidget->range().x(); 0372 EffectsController::offsetKeyframes(in, effect); 0373 } 0374 */ 0375 return effect; 0376 } 0377 0378 bool CollapsibleEffectView::isActive() const 0379 { 0380 return decoframe->property("active").toBool(); 0381 } 0382 0383 bool CollapsibleEffectView::isEnabled() const 0384 { 0385 return m_enabledButton->isActive(); 0386 } 0387 0388 void CollapsibleEffectView::slotActivateEffect(bool active) 0389 { 0390 decoframe->setProperty("active", active); 0391 decoframe->setStyleSheet(decoframe->styleSheet()); 0392 if (active) { 0393 pCore->getMonitor(m_model->monitorId)->slotShowEffectScene(needsMonitorEffectScene()); 0394 } 0395 Q_EMIT m_view->initKeyframeView(active); 0396 if (m_inOutButton->isChecked()) { 0397 Q_EMIT showEffectZone(m_model->getOwnerId(), m_model->getInOut(), true); 0398 } else { 0399 Q_EMIT showEffectZone(m_model->getOwnerId(), {0, 0}, false); 0400 } 0401 } 0402 0403 void CollapsibleEffectView::mousePressEvent(QMouseEvent *e) 0404 { 0405 qDebug() << "XXXX COLLAPSIBLE PRESS EVENT...."; 0406 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0407 m_dragStart = e->globalPos(); 0408 #else 0409 m_dragStart = e->globalPosition().toPoint(); 0410 #endif 0411 m_dragging = false; 0412 if (!decoframe->property("active").toBool()) { 0413 // Activate effect if not already active 0414 Q_EMIT activateEffect(m_model->row()); 0415 } 0416 QWidget::mousePressEvent(e); 0417 } 0418 0419 void CollapsibleEffectView::wheelEvent(QWheelEvent *e) 0420 { 0421 if (m_blockWheel) { 0422 // initiating a wheel event in an empty space will clear focus 0423 setFocus(); 0424 } 0425 QWidget::wheelEvent(e); 0426 } 0427 0428 void CollapsibleEffectView::mouseMoveEvent(QMouseEvent *e) 0429 { 0430 qDebug() << "XXXX COLLAPSIBLE MOVE EVENT...."; 0431 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0432 if (!m_dragging && (e->globalPos() - m_dragStart).manhattanLength() > QApplication::startDragDistance()) { 0433 #else 0434 if (!m_dragging && (e->globalPosition().toPoint() - m_dragStart).manhattanLength() > QApplication::startDragDistance()) { 0435 #endif 0436 m_dragging = true; 0437 QPixmap pix = frame->grab(); 0438 Q_EMIT startDrag(pix, m_model->getAssetId(), m_model->getOwnerId(), m_model->row()); 0439 } 0440 QWidget::mouseMoveEvent(e); 0441 } 0442 0443 void CollapsibleEffectView::mouseDoubleClickEvent(QMouseEvent *event) 0444 { 0445 if (frame->underMouse() && collapseButton->isEnabled()) { 0446 event->accept(); 0447 m_collapse->setActive(!m_collapse->isActive()); 0448 } else { 0449 event->ignore(); 0450 } 0451 } 0452 0453 void CollapsibleEffectView::mouseReleaseEvent(QMouseEvent *event) 0454 { 0455 qDebug() << "XXXX COLLAPSIBLE RELASE EVENT...."; 0456 m_dragStart = QPoint(); 0457 m_dragging = false; 0458 if (!decoframe->property("active").toBool()) { 0459 // Q_EMIT activateEffect(effectIndex()); 0460 } 0461 QWidget::mouseReleaseEvent(event); 0462 } 0463 0464 void CollapsibleEffectView::slotDisable(bool disable) 0465 { 0466 QString effectId = m_model->getAssetId(); 0467 QString effectName = EffectsRepository::get()->getName(effectId); 0468 std::static_pointer_cast<AbstractEffectItem>(m_model)->markEnabled(effectName, !disable); 0469 pCore->getMonitor(m_model->monitorId)->slotShowEffectScene(needsMonitorEffectScene()); 0470 Q_EMIT m_view->initKeyframeView(!disable); 0471 Q_EMIT activateEffect(m_model->row()); 0472 } 0473 0474 void CollapsibleEffectView::updateScene() 0475 { 0476 pCore->getMonitor(m_model->monitorId)->slotShowEffectScene(needsMonitorEffectScene()); 0477 Q_EMIT m_view->initKeyframeView(m_model->isEnabled()); 0478 } 0479 0480 void CollapsibleEffectView::slotDeleteEffect() 0481 { 0482 Q_EMIT deleteEffect(m_model); 0483 } 0484 0485 void CollapsibleEffectView::slotEffectUp() 0486 { 0487 Q_EMIT moveEffect(qMax(0, m_model->row() - 1), m_model); 0488 } 0489 0490 void CollapsibleEffectView::slotEffectDown() 0491 { 0492 Q_EMIT moveEffect(m_model->row() + 2, m_model); 0493 } 0494 0495 void CollapsibleEffectView::slotSaveEffect() 0496 { 0497 QDialog dialog(this); 0498 QFormLayout form(&dialog); 0499 0500 dialog.setWindowTitle(i18nc("@title:window", "Save Effect")); 0501 0502 auto *effectName = new QLineEdit(&dialog); 0503 auto *descriptionBox = new QTextEdit(&dialog); 0504 form.addRow(i18n("Name:"), effectName); 0505 form.addRow(i18n("Comments:"), descriptionBox); 0506 0507 QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); 0508 form.addRow(&buttonBox); 0509 0510 QObject::connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); 0511 QObject::connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); 0512 0513 if (dialog.exec() == QDialog::Accepted) { 0514 QString name = effectName->text(); 0515 QString enteredDescription = descriptionBox->toPlainText(); 0516 if (name.trimmed().isEmpty()) { 0517 KMessageBox::error(this, i18n("No name provided, effect not saved.")); 0518 return; 0519 } 0520 QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/")); 0521 if (!dir.exists()) { 0522 dir.mkpath(QStringLiteral(".")); 0523 } 0524 0525 if (dir.exists(name + QStringLiteral(".xml"))) 0526 if (KMessageBox::questionTwoActions(this, i18n("File %1 already exists.\nDo you want to overwrite it?", name + QStringLiteral(".xml")), {}, 0527 KStandardGuiItem::overwrite(), KStandardGuiItem::cancel()) == KMessageBox::SecondaryAction) { 0528 return; 0529 } 0530 0531 QDomDocument doc; 0532 // Get base effect xml 0533 QString effectId = m_model->getAssetId(); 0534 QDomElement effect = EffectsRepository::get()->getXml(effectId); 0535 // Adjust param values 0536 QVector<QPair<QString, QVariant>> currentValues = m_model->getAllParameters(); 0537 QMap<QString, QString> values; 0538 for (const auto ¶m : qAsConst(currentValues)) { 0539 values.insert(param.first, param.second.toString()); 0540 } 0541 QDomNodeList params = effect.elementsByTagName("parameter"); 0542 for (int i = 0; i < params.count(); ++i) { 0543 const QString paramName = params.item(i).toElement().attribute("name"); 0544 const QString paramType = params.item(i).toElement().attribute("type"); 0545 if (paramType == QLatin1String("fixed") || !values.contains(paramName)) { 0546 continue; 0547 } 0548 if (paramType == QLatin1String("multiswitch")) { 0549 // Multiswitch param value is not updated on change, fo fetch real value now 0550 QString val = m_model->getParamFromName(paramName).toString(); 0551 params.item(i).toElement().setAttribute(QStringLiteral("value"), val); 0552 continue; 0553 } 0554 params.item(i).toElement().setAttribute(QStringLiteral("value"), values.value(paramName)); 0555 } 0556 doc.appendChild(doc.importNode(effect, true)); 0557 effect = doc.firstChild().toElement(); 0558 effect.removeAttribute(QStringLiteral("kdenlive_ix")); 0559 QString namedId = name; 0560 QString sourceId = effect.attribute("id"); 0561 // When saving an effect as custom, it might be necessary to keep track of the original 0562 // effect id as it is sometimes used in Kdenlive to trigger special behaviors 0563 if (sourceId.startsWith(QStringLiteral("fade_to_"))) { 0564 namedId.prepend(QStringLiteral("fade_to_")); 0565 } else if (sourceId.startsWith(QStringLiteral("fade_from_"))) { 0566 namedId.prepend(QStringLiteral("fade_from_")); 0567 } 0568 if (sourceId.startsWith(QStringLiteral("fadein"))) { 0569 namedId.prepend(QStringLiteral("fadein_")); 0570 } 0571 if (sourceId.startsWith(QStringLiteral("fadeout"))) { 0572 namedId.prepend(QStringLiteral("fadeout_")); 0573 } 0574 effect.setAttribute(QStringLiteral("id"), namedId); 0575 effect.setAttribute(QStringLiteral("type"), m_model->isAudio() ? QStringLiteral("customAudio") : QStringLiteral("customVideo")); 0576 0577 QDomElement effectname = effect.firstChildElement(QStringLiteral("name")); 0578 effect.removeChild(effectname); 0579 effectname = doc.createElement(QStringLiteral("name")); 0580 QDomText nametext = doc.createTextNode(name); 0581 effectname.appendChild(nametext); 0582 effect.insertBefore(effectname, QDomNode()); 0583 QDomElement effectprops = effect.firstChildElement(QStringLiteral("properties")); 0584 effectprops.setAttribute(QStringLiteral("id"), name); 0585 effectprops.setAttribute(QStringLiteral("type"), QStringLiteral("custom")); 0586 QFile file(dir.absoluteFilePath(name + QStringLiteral(".xml"))); 0587 0588 if (!enteredDescription.trimmed().isEmpty()) { 0589 QDomElement root = doc.documentElement(); 0590 QDomElement nodelist = root.firstChildElement("description"); 0591 QDomElement newNodeTag = doc.createElement(QString("description")); 0592 QDomText text = doc.createTextNode(enteredDescription); 0593 newNodeTag.appendChild(text); 0594 root.replaceChild(newNodeTag, nodelist); 0595 } 0596 0597 if (file.open(QFile::WriteOnly | QFile::Truncate)) { 0598 QTextStream out(&file); 0599 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0600 out.setCodec("UTF-8"); 0601 #endif 0602 out << doc.toString(); 0603 } else { 0604 KMessageBox::error(this, i18n("Cannot write to file %1", file.fileName())); 0605 } 0606 file.close(); 0607 Q_EMIT reloadEffect(dir.absoluteFilePath(name + QStringLiteral(".xml"))); 0608 } 0609 } 0610 0611 QDomDocument CollapsibleEffectView::toXml() const 0612 { 0613 QDomDocument doc; 0614 // Get base effect xml 0615 QString effectId = m_model->getAssetId(); 0616 // Adjust param values 0617 QVector<QPair<QString, QVariant>> currentValues = m_model->getAllParameters(); 0618 0619 QDomElement effect = doc.createElement(QStringLiteral("effect")); 0620 doc.appendChild(effect); 0621 effect.setAttribute(QStringLiteral("id"), effectId); 0622 for (const auto ¶m : qAsConst(currentValues)) { 0623 QDomElement xmlParam = doc.createElement(QStringLiteral("property")); 0624 effect.appendChild(xmlParam); 0625 xmlParam.setAttribute(QStringLiteral("name"), param.first); 0626 QString value; 0627 value = param.second.toString(); 0628 QDomText val = doc.createTextNode(value); 0629 xmlParam.appendChild(val); 0630 } 0631 return doc; 0632 } 0633 0634 void CollapsibleEffectView::slotResetEffect() 0635 { 0636 m_view->resetValues(); 0637 } 0638 0639 void CollapsibleEffectView::updateHeight() 0640 { 0641 if (m_view->height() == widgetFrame->height()) { 0642 return; 0643 } 0644 widgetFrame->setFixedHeight(m_collapse->isActive() ? 0 : m_view->height()); 0645 setFixedHeight(widgetFrame->height() + frame->minimumHeight() + zoneFrame->minimumHeight() + 2 * (contentsMargins().top() + decoframe->lineWidth())); 0646 Q_EMIT switchHeight(m_model, height()); 0647 } 0648 0649 void CollapsibleEffectView::switchCollapsed(int row) 0650 { 0651 if (row == m_model->row()) { 0652 slotSwitch(!m_model->isCollapsed()); 0653 } 0654 } 0655 0656 void CollapsibleEffectView::slotSwitch(bool collapse) 0657 { 0658 widgetFrame->setFixedHeight(collapse ? 0 : m_view->height()); 0659 zoneFrame->setFixedHeight(collapse || !m_inOutButton->isChecked() ? 0 : frame->height()); 0660 setFixedHeight(widgetFrame->height() + frame->minimumHeight() + zoneFrame->height() + 2 * (contentsMargins().top() + decoframe->lineWidth())); 0661 m_model->setCollapsed(collapse); 0662 Q_EMIT switchHeight(m_model, height()); 0663 } 0664 0665 void CollapsibleEffectView::setGroupIndex(int ix) 0666 { 0667 Q_UNUSED(ix) 0668 /*if (m_info.groupIndex == -1 && ix != -1) { 0669 m_menu->removeAction(m_groupAction); 0670 } else if (m_info.groupIndex != -1 && ix == -1) { 0671 m_menu->addAction(m_groupAction); 0672 } 0673 m_info.groupIndex = ix; 0674 m_effect.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString());*/ 0675 } 0676 0677 void CollapsibleEffectView::setGroupName(const QString &groupName) 0678 { 0679 Q_UNUSED(groupName) 0680 /*m_info.groupName = groupName; 0681 m_effect.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString());*/ 0682 } 0683 0684 QString CollapsibleEffectView::infoString() const 0685 { 0686 return QString(); // m_info.toString(); 0687 } 0688 0689 void CollapsibleEffectView::removeFromGroup() 0690 { 0691 /*if (m_info.groupIndex != -1) { 0692 m_menu->addAction(m_groupAction); 0693 } 0694 m_info.groupIndex = -1; 0695 m_info.groupName.clear(); 0696 m_effect.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString()); 0697 Q_EMIT parameterChanged(m_original_effect, m_effect, effectIndex());*/ 0698 } 0699 0700 int CollapsibleEffectView::groupIndex() const 0701 { 0702 return -1; // m_info.groupIndex; 0703 } 0704 0705 int CollapsibleEffectView::effectIndex() const 0706 { 0707 if (m_effect.isNull()) { 0708 return -1; 0709 } 0710 return m_effect.attribute(QStringLiteral("kdenlive_ix")).toInt(); 0711 } 0712 0713 void CollapsibleEffectView::updateWidget(const ItemInfo &info, const QDomElement &effect) 0714 { 0715 Q_UNUSED(info) 0716 // cleanup 0717 /* 0718 delete m_paramWidget; 0719 m_paramWidget = nullptr; 0720 */ 0721 m_effect = effect; 0722 } 0723 0724 void CollapsibleEffectView::updateFrameInfo() 0725 { 0726 /* 0727 if (m_paramWidget) { 0728 m_paramWidget->refreshFrameInfo(); 0729 } 0730 */ 0731 } 0732 0733 void CollapsibleEffectView::setActiveKeyframe(int kf) 0734 { 0735 Q_UNUSED(kf) 0736 /* 0737 if (m_paramWidget) { 0738 m_paramWidget->setActiveKeyframe(kf); 0739 } 0740 */ 0741 } 0742 0743 bool CollapsibleEffectView::isGroup() const 0744 { 0745 return false; 0746 } 0747 0748 void CollapsibleEffectView::updateTimecodeFormat() 0749 { 0750 /* 0751 m_paramWidget->updateTimecodeFormat(); 0752 if (!m_subParamWidgets.isEmpty()) { 0753 // we have a group 0754 for (int i = 0; i < m_subParamWidgets.count(); ++i) { 0755 m_subParamWidgets.at(i)->updateTimecodeFormat(); 0756 } 0757 } 0758 */ 0759 } 0760 0761 void CollapsibleEffectView::slotUpdateRegionEffectParams(const QDomElement & /*old*/, const QDomElement & /*e*/, int /*ix*/) 0762 { 0763 // qCDebug(KDENLIVE_LOG)<<"// EMIT CHANGE SUBEFFECT.....:"; 0764 Q_EMIT parameterChanged(m_original_effect, m_effect, effectIndex()); 0765 } 0766 0767 void CollapsibleEffectView::slotSyncEffectsPos(int pos) 0768 { 0769 Q_EMIT syncEffectsPos(pos); 0770 } 0771 0772 void CollapsibleEffectView::dragEnterEvent(QDragEnterEvent *event) 0773 { 0774 Q_UNUSED(event) 0775 /* 0776 if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/effectslist"))) { 0777 frame->setProperty("target", true); 0778 frame->setStyleSheet(frame->styleSheet()); 0779 event->acceptProposedAction(); 0780 } else if (m_paramWidget->doesAcceptDrops() && event->mimeData()->hasFormat(QStringLiteral("kdenlive/geometry")) && 0781 event->source()->objectName() != QStringLiteral("ParameterContainer")) { 0782 event->setDropAction(Qt::CopyAction); 0783 event->setAccepted(true); 0784 } else { 0785 QWidget::dragEnterEvent(event); 0786 } 0787 */ 0788 } 0789 0790 void CollapsibleEffectView::dragLeaveEvent(QDragLeaveEvent * /*event*/) 0791 { 0792 frame->setProperty("target", false); 0793 frame->setStyleSheet(frame->styleSheet()); 0794 } 0795 0796 void CollapsibleEffectView::importKeyframes(const QString &kf) 0797 { 0798 QMap<QString, QString> keyframes; 0799 if (kf.contains(QLatin1Char('\n'))) { 0800 const QStringList params = kf.split(QLatin1Char('\n'), Qt::SkipEmptyParts); 0801 for (const QString ¶m : params) { 0802 keyframes.insert(param.section(QLatin1Char('='), 0, 0), param.section(QLatin1Char('='), 1)); 0803 } 0804 } else { 0805 keyframes.insert(kf.section(QLatin1Char('='), 0, 0), kf.section(QLatin1Char('='), 1)); 0806 } 0807 Q_EMIT importClipKeyframes(AVWidget, m_itemInfo, m_effect.cloneNode().toElement(), keyframes); 0808 } 0809 0810 void CollapsibleEffectView::dropEvent(QDropEvent *event) 0811 { 0812 if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/geometry"))) { 0813 if (event->source()->objectName() == QStringLiteral("ParameterContainer")) { 0814 return; 0815 } 0816 QString itemData = event->mimeData()->data(QStringLiteral("kdenlive/geometry")); 0817 importKeyframes(itemData); 0818 return; 0819 } 0820 frame->setProperty("target", false); 0821 frame->setStyleSheet(frame->styleSheet()); 0822 const QString effects = QString::fromUtf8(event->mimeData()->data(QStringLiteral("kdenlive/effectslist"))); 0823 // event->acceptProposedAction(); 0824 QDomDocument doc; 0825 doc.setContent(effects, true); 0826 QDomElement e = doc.documentElement(); 0827 int ix = e.attribute(QStringLiteral("kdenlive_ix")).toInt(); 0828 int currentEffectIx = effectIndex(); 0829 if (ix == currentEffectIx || e.attribute(QStringLiteral("id")) == QLatin1String("speed")) { 0830 // effect dropped on itself, or unmovable speed dropped, reject 0831 event->ignore(); 0832 return; 0833 } 0834 if (ix == 0 || e.tagName() == QLatin1String("effectgroup")) { 0835 if (e.tagName() == QLatin1String("effectgroup")) { 0836 // moving a group 0837 QDomNodeList subeffects = e.elementsByTagName(QStringLiteral("effect")); 0838 if (subeffects.isEmpty()) { 0839 event->ignore(); 0840 return; 0841 } 0842 event->setDropAction(Qt::MoveAction); 0843 event->accept(); 0844 Q_EMIT addEffect(e); 0845 return; 0846 } 0847 // effect dropped from effects list, add it 0848 e.setAttribute(QStringLiteral("kdenlive_ix"), ix); 0849 /*if (m_info.groupIndex > -1) { 0850 // Dropped on a group 0851 e.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString()); 0852 }*/ 0853 event->setDropAction(Qt::CopyAction); 0854 event->accept(); 0855 Q_EMIT addEffect(e); 0856 return; 0857 } 0858 // Q_EMIT moveEffect(QList<int>() << ix, currentEffectIx, m_info.groupIndex, m_info.groupName); 0859 event->setDropAction(Qt::MoveAction); 0860 event->accept(); 0861 } 0862 0863 void CollapsibleEffectView::adjustButtons(int ix, int max) 0864 { 0865 buttonUp->setEnabled(ix > 0); 0866 buttonDown->setEnabled(ix < max - 1); 0867 } 0868 0869 MonitorSceneType CollapsibleEffectView::needsMonitorEffectScene() const 0870 { 0871 if (!m_model->isEnabled() || !m_view) { 0872 return MonitorSceneDefault; 0873 } 0874 return m_view->needsMonitorEffectScene(); 0875 } 0876 0877 void CollapsibleEffectView::setKeyframes(const QString &tag, const QString &keyframes) 0878 { 0879 Q_UNUSED(tag) 0880 Q_UNUSED(keyframes) 0881 /* 0882 m_paramWidget->setKeyframes(tag, keyframes); 0883 */ 0884 } 0885 0886 bool CollapsibleEffectView::isMovable() const 0887 { 0888 return m_isMovable; 0889 } 0890 0891 void CollapsibleEffectView::prepareImportClipKeyframes() 0892 { 0893 Q_EMIT importClipKeyframes(AVWidget, m_itemInfo, m_effect.cloneNode().toElement(), QMap<QString, QString>()); 0894 } 0895 0896 void CollapsibleEffectView::enableView(bool enabled) 0897 { 0898 m_enabledButton->setActive(enabled); 0899 title->setEnabled(!enabled); 0900 if (enabled) { 0901 if (KdenliveSettings::disable_effect_parameters()) { 0902 widgetFrame->setEnabled(false); 0903 } 0904 } else { 0905 widgetFrame->setEnabled(true); 0906 } 0907 } 0908 0909 void CollapsibleEffectView::enableHideKeyframes(bool enabled) 0910 { 0911 m_keyframesButton->setActive(enabled); 0912 m_view->toggleKeyframes(!enabled); 0913 } 0914 0915 void CollapsibleEffectView::blockWheelEvent(bool block) 0916 { 0917 m_blockWheel = block; 0918 Qt::FocusPolicy policy = block ? Qt::StrongFocus : Qt::WheelFocus; 0919 for (QSpinBox *sp : findChildren<QSpinBox *>()) { 0920 sp->installEventFilter(this); 0921 sp->setFocusPolicy(policy); 0922 } 0923 for (QComboBox *cb : findChildren<QComboBox *>()) { 0924 cb->installEventFilter(this); 0925 cb->setFocusPolicy(policy); 0926 } 0927 for (QProgressBar *cb : findChildren<QProgressBar *>()) { 0928 cb->installEventFilter(this); 0929 cb->setFocusPolicy(policy); 0930 } 0931 for (WheelContainer *cb : findChildren<WheelContainer *>()) { 0932 cb->installEventFilter(this); 0933 cb->setFocusPolicy(policy); 0934 } 0935 for (QDoubleSpinBox *cb : findChildren<QDoubleSpinBox *>()) { 0936 cb->installEventFilter(this); 0937 cb->setFocusPolicy(policy); 0938 } 0939 for (KeyframeWidget *cb : findChildren<KeyframeWidget *>()) { 0940 for (KeyframeView *cb2 : cb->findChildren<KeyframeView *>()) { 0941 cb2->installEventFilter(this); 0942 cb2->setFocusPolicy(policy); 0943 } 0944 } 0945 } 0946 0947 void CollapsibleEffectView::switchInOut(bool checked) 0948 { 0949 QString effectId = m_model->getAssetId(); 0950 QString effectName = EffectsRepository::get()->getName(effectId); 0951 QPair<int, int> inOut = m_model->getInOut(); 0952 zoneFrame->setFixedHeight(checked ? frame->height() : 0); 0953 slotSwitch(m_collapse->isActive()); 0954 if (inOut.first == inOut.second || !checked) { 0955 ObjectId owner = m_model->getOwnerId(); 0956 switch (owner.type) { 0957 case KdenliveObjectType::TimelineClip: { 0958 int in = pCore->getItemIn(owner); 0959 inOut = {in, in + pCore->getItemDuration(owner)}; 0960 break; 0961 } 0962 case KdenliveObjectType::TimelineTrack: 0963 case KdenliveObjectType::Master: { 0964 if (!checked) { 0965 inOut = {0, 0}; 0966 } else { 0967 int in = pCore->getMonitorPosition(); 0968 inOut = {in, in + pCore->getDurationFromString(KdenliveSettings::transition_duration())}; 0969 } 0970 break; 0971 } 0972 default: 0973 qDebug() << "== UNSUPPORTED ITEM TYPE FOR EFFECT RANGE: " << int(owner.type); 0974 break; 0975 } 0976 } 0977 qDebug() << "==== SWITCHING IN / OUT: " << inOut.first << "-" << inOut.second; 0978 if (inOut.first > -1) { 0979 m_model->setInOut(effectName, inOut, checked, true); 0980 m_inPos->setValue(inOut.first); 0981 m_outPos->setValue(inOut.second); 0982 } 0983 } 0984 0985 void CollapsibleEffectView::updateInOut(QPair<int, int> inOut, bool withUndo) 0986 { 0987 if (!m_inOutButton->isChecked()) { 0988 qDebug() << "=== CANNOT UPDATE ZONE ON EFFECT!!!"; 0989 return; 0990 } 0991 QString effectId = m_model->getAssetId(); 0992 QString effectName = EffectsRepository::get()->getName(effectId); 0993 if (inOut.first > -1) { 0994 m_model->setInOut(effectName, inOut, true, withUndo); 0995 m_inPos->setValue(inOut.first); 0996 m_outPos->setValue(inOut.second); 0997 } 0998 } 0999 1000 void CollapsibleEffectView::updateEffectZone() 1001 { 1002 QString effectId = m_model->getAssetId(); 1003 QString effectName = EffectsRepository::get()->getName(effectId); 1004 QPair<int, int> inOut = {m_inPos->getValue(), m_outPos->getValue()}; 1005 m_model->setInOut(effectName, inOut, true, true); 1006 } 1007 1008 void CollapsibleEffectView::slotNextKeyframe() 1009 { 1010 Q_EMIT m_view->nextKeyframe(); 1011 } 1012 1013 void CollapsibleEffectView::slotPreviousKeyframe() 1014 { 1015 Q_EMIT m_view->previousKeyframe(); 1016 } 1017 1018 void CollapsibleEffectView::addRemoveKeyframe() 1019 { 1020 Q_EMIT m_view->addRemoveKeyframe(); 1021 } 1022 1023 void CollapsibleEffectView::slotHideKeyframes(bool hide) 1024 { 1025 m_model->setKeyframesHidden(hide); 1026 } 1027 1028 void CollapsibleEffectView::sendStandardCommand(int command) 1029 { 1030 m_view->sendStandardCommand(command); 1031 }