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 &param : 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 &param : 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 &param : 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 }