File indexing completed on 2024-05-05 04:52:51

0001 /*
0002     SPDX-FileCopyrightText: 2017 Nicolas Carion
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "assetparameterview.hpp"
0007 
0008 #include "assets/model/assetcommand.hpp"
0009 #include "assets/model/assetparametermodel.hpp"
0010 #include "assets/view/widgets/abstractparamwidget.hpp"
0011 #include "assets/view/widgets/keyframewidget.hpp"
0012 #include "core.h"
0013 #include "monitor/monitor.h"
0014 
0015 #include <QActionGroup>
0016 #include <QDebug>
0017 #include <QDir>
0018 #include <QFontDatabase>
0019 #include <QInputDialog>
0020 #include <QMenu>
0021 #include <QStandardPaths>
0022 #include <QVBoxLayout>
0023 #include <utility>
0024 
0025 AssetParameterView::AssetParameterView(QWidget *parent)
0026     : QWidget(parent)
0027 
0028 {
0029     m_lay = new QVBoxLayout(this);
0030     m_lay->setContentsMargins(0, 0, 0, 2);
0031     m_lay->setSpacing(0);
0032     setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0033     // Presets Combo
0034     m_presetMenu = new QMenu(this);
0035 }
0036 
0037 void AssetParameterView::setModel(const std::shared_ptr<AssetParameterModel> &model, QSize frameSize, bool addSpacer)
0038 {
0039     unsetModel();
0040     QMutexLocker lock(&m_lock);
0041     m_model = model;
0042     setSizePolicy(QSizePolicy::Preferred, addSpacer ? QSizePolicy::Preferred : QSizePolicy::Fixed);
0043     const QString paramTag = model->getAssetId();
0044     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
0045     const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(paramTag));
0046     connect(this, &AssetParameterView::updatePresets, this, [this, presetFile](const QString &presetName) {
0047         m_presetMenu->clear();
0048         m_presetGroup.reset(new QActionGroup(this));
0049         m_presetGroup->setExclusive(true);
0050         m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reset Effect"), this, &AssetParameterView::resetValues);
0051         // Save preset
0052         m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save-as-template")), i18n("Save preset…"), this, [&]() { slotSavePreset(); });
0053         QAction *updatePreset = m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save-as-template")), i18n("Update current preset"), this,
0054                                                         &AssetParameterView::slotUpdatePreset);
0055         QAction *deletePreset =
0056             m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete preset"), this, &AssetParameterView::slotDeleteCurrentPreset);
0057         m_presetMenu->setWhatsThis(xi18nc("@info:whatsthis", "Deletes the currently selected preset."));
0058         m_presetMenu->addSeparator();
0059         QStringList presets = m_model->getPresetList(presetFile);
0060         if (presetName.isEmpty() || presets.isEmpty()) {
0061             updatePreset->setEnabled(false);
0062             deletePreset->setEnabled(false);
0063         }
0064         for (const QString &pName : qAsConst(presets)) {
0065             QAction *ac = m_presetMenu->addAction(pName, this, &AssetParameterView::slotLoadPreset);
0066             m_presetGroup->addAction(ac);
0067             ac->setData(pName);
0068             ac->setCheckable(true);
0069             if (pName == presetName) {
0070                 ac->setChecked(true);
0071             }
0072         }
0073     });
0074     Q_EMIT updatePresets();
0075     connect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh);
0076     int minHeight = 0;
0077     for (int i = 0; i < model->rowCount(); ++i) {
0078         QModelIndex index = model->index(i, 0);
0079         auto type = model->data(index, AssetParameterModel::TypeRole).value<ParamType>();
0080         if (m_mainKeyframeWidget && (AssetParameterModel::isAnimated(type) || type == ParamType::Geometry)) {
0081             // Keyframe widget can have some extra params that shouldn't build a new widget
0082             qDebug() << "// FOUND ADDED PARAM";
0083             if (type != ParamType::ColorWheel) {
0084                 m_mainKeyframeWidget->addParameter(index);
0085             }
0086         } else {
0087             auto *w = AbstractParamWidget::construct(model, index, frameSize, this);
0088             connect(this, &AssetParameterView::initKeyframeView, w, &AbstractParamWidget::slotInitMonitor);
0089             connect(w, &AbstractParamWidget::valueChanged, this, &AssetParameterView::commitChanges);
0090             connect(w, &AbstractParamWidget::disableCurrentFilter, this, &AssetParameterView::disableCurrentFilter);
0091             connect(w, &AbstractParamWidget::seekToPos, this, &AssetParameterView::seekToPos);
0092             connect(w, &AbstractParamWidget::activateEffect, this, &AssetParameterView::activateEffect);
0093             connect(w, &AbstractParamWidget::updateHeight, this, [&]() {
0094                 setFixedHeight(contentHeight());
0095                 Q_EMIT updateHeight();
0096             });
0097             if (AssetParameterModel::isAnimated(type)) {
0098                 m_mainKeyframeWidget = static_cast<KeyframeWidget *>(w);
0099                 connect(this, &AssetParameterView::nextKeyframe, m_mainKeyframeWidget, &KeyframeWidget::goToNext);
0100                 connect(this, &AssetParameterView::previousKeyframe, m_mainKeyframeWidget, &KeyframeWidget::goToPrevious);
0101                 connect(this, &AssetParameterView::addRemoveKeyframe, m_mainKeyframeWidget, &KeyframeWidget::addRemove);
0102                 connect(this, &AssetParameterView::sendStandardCommand, m_mainKeyframeWidget, &KeyframeWidget::sendStandardCommand);
0103             } else {
0104                 m_lay->addWidget(w);
0105                 minHeight += w->minimumHeight();
0106             }
0107             m_widgets.push_back(w);
0108         }
0109     }
0110     if (m_mainKeyframeWidget) {
0111         // Add keyframe widget to the bottom to have a clear seperation
0112         // between animated an non-animated params
0113         m_lay->addWidget(m_mainKeyframeWidget);
0114         minHeight += m_mainKeyframeWidget->minimumHeight();
0115     }
0116     setMinimumHeight(minHeight);
0117     if (addSpacer) {
0118         m_lay->addStretch();
0119     }
0120     // Ensure effect parameters are adjusted to current position
0121     Monitor *monitor = pCore->getMonitor(m_model->monitorId);
0122     Q_EMIT monitor->seekPosition(monitor->position());
0123 }
0124 
0125 QVector<QPair<QString, QVariant>> AssetParameterView::getDefaultValues() const
0126 {
0127     QVector<QPair<QString, QVariant>> values;
0128     for (int i = 0; i < m_model->rowCount(); ++i) {
0129         QModelIndex index = m_model->index(i, 0);
0130         QString name = m_model->data(index, AssetParameterModel::NameRole).toString();
0131         auto type = m_model->data(index, AssetParameterModel::TypeRole).value<ParamType>();
0132         QVariant defaultValue = m_model->data(index, AssetParameterModel::DefaultRole);
0133         if (AssetParameterModel::isAnimated(type) && type != ParamType::Roto_spline) {
0134             // Roto_spline keyframes are stored as JSON so do not apply this to roto
0135             QString val = defaultValue.toString();
0136             if (!val.contains(QLatin1Char('='))) {
0137                 val.prepend(QStringLiteral("%1=").arg(m_model->data(index, AssetParameterModel::ParentInRole).toInt()));
0138                 defaultValue = QVariant(val);
0139             }
0140         }
0141         values.append({name, defaultValue});
0142     }
0143     return values;
0144 }
0145 
0146 void AssetParameterView::resetValues()
0147 {
0148     const QVector<QPair<QString, QVariant>> values = getDefaultValues();
0149     auto *command = new AssetUpdateCommand(m_model, values);
0150     if (m_model->getOwnerId().itemId != -1) {
0151         pCore->pushUndo(command);
0152     } else {
0153         command->redo();
0154         delete command;
0155     }
0156     // Unselect preset if any
0157     QAction *ac = m_presetGroup->checkedAction();
0158     if (ac) {
0159         ac->setChecked(false);
0160         ;
0161     }
0162 }
0163 
0164 void AssetParameterView::disableCurrentFilter(bool disable)
0165 {
0166     m_model->setParameter(QStringLiteral("disable"), disable ? 1 : 0, true);
0167 }
0168 
0169 void AssetParameterView::commitChanges(const QModelIndex &index, const QString &value, bool storeUndo)
0170 {
0171     // Warning: please note that some widgets (for example keyframes) do NOT send the valueChanged signal and do modifications on their own
0172     auto *command = new AssetCommand(m_model, index, value);
0173     if (storeUndo && m_model->getOwnerId().itemId != -1) {
0174         pCore->pushUndo(command);
0175     } else {
0176         command->redo();
0177         delete command;
0178     }
0179 }
0180 
0181 void AssetParameterView::commitMultipleChanges(const QList<QModelIndex> &indexes, const QStringList &values, bool storeUndo)
0182 {
0183     // Warning: please note that some widgets (for example keyframes) do NOT send the valueChanged signal and do modifications on their own
0184     auto *command = new AssetMultiCommand(m_model, indexes, values);
0185     if (storeUndo) {
0186         pCore->pushUndo(command);
0187     } else {
0188         command->redo();
0189         delete command;
0190     }
0191 }
0192 
0193 void AssetParameterView::unsetModel()
0194 {
0195     QMutexLocker lock(&m_lock);
0196     if (m_model) {
0197         // if a model is already there, we have to disconnect signals first
0198         disconnect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh);
0199     }
0200     m_mainKeyframeWidget = nullptr;
0201 
0202     // clear layout
0203     m_widgets.clear();
0204     QLayoutItem *child = nullptr;
0205     while ((child = m_lay->takeAt(0)) != nullptr) {
0206         if (child->layout()) {
0207             QLayoutItem *subchild = nullptr;
0208             while ((subchild = child->layout()->takeAt(0)) != nullptr) {
0209                 delete subchild->widget();
0210                 delete subchild->spacerItem();
0211             }
0212         }
0213         delete child->widget();
0214         delete child->spacerItem();
0215     }
0216 
0217     // Release ownership of smart pointer
0218     m_model.reset();
0219 }
0220 
0221 void AssetParameterView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
0222 {
0223     QMutexLocker lock(&m_lock);
0224     if (m_widgets.size() == 0) {
0225         // no visible param for this asset, abort
0226         return;
0227     }
0228     Q_UNUSED(roles);
0229     // We are expecting indexes that are children of the root index, which is "invalid"
0230     Q_ASSERT(!topLeft.parent().isValid());
0231     // We make sure the range is valid
0232     auto type = m_model->data(m_model->index(topLeft.row(), 0), AssetParameterModel::TypeRole).value<ParamType>();
0233     if (type == ParamType::ColorWheel) {
0234         // Some special widgets, like colorwheel handle multiple params so we can have cases where param index row is greater than the number of widgets.
0235         // Should be better managed
0236         m_widgets[0]->slotRefresh();
0237         return;
0238     }
0239     size_t max = m_widgets.size() - 1;
0240     if (bottomRight.isValid()) {
0241         max = qMin(max, size_t(bottomRight.row()));
0242     }
0243     Q_ASSERT(max < m_widgets.size());
0244     for (auto i = size_t(topLeft.row()); i <= max; ++i) {
0245         m_widgets.at(i)->slotRefresh();
0246     }
0247 }
0248 
0249 int AssetParameterView::contentHeight() const
0250 {
0251     return m_lay->minimumSize().height();
0252 }
0253 
0254 MonitorSceneType AssetParameterView::needsMonitorEffectScene() const
0255 {
0256     if (m_mainKeyframeWidget) {
0257         return m_mainKeyframeWidget->requiredScene();
0258     }
0259     for (int i = 0; i < m_model->rowCount(); ++i) {
0260         QModelIndex index = m_model->index(i, 0);
0261         auto type = m_model->data(index, AssetParameterModel::TypeRole).value<ParamType>();
0262         if (type == ParamType::Geometry) {
0263             return MonitorSceneGeometry;
0264         }
0265     }
0266     return MonitorSceneDefault;
0267 }
0268 
0269 void AssetParameterView::slotRefresh()
0270 {
0271     refresh(m_model->index(0, 0), m_model->index(m_model->rowCount() - 1, 0), {});
0272 }
0273 
0274 bool AssetParameterView::keyframesAllowed() const
0275 {
0276     return m_mainKeyframeWidget != nullptr;
0277 }
0278 
0279 bool AssetParameterView::modelHideKeyframes() const
0280 {
0281     return m_mainKeyframeWidget != nullptr && !m_mainKeyframeWidget->keyframesVisible();
0282 }
0283 
0284 void AssetParameterView::toggleKeyframes(bool enable)
0285 {
0286     if (m_mainKeyframeWidget) {
0287         m_mainKeyframeWidget->showKeyframes(enable);
0288         setFixedHeight(contentHeight());
0289         Q_EMIT updateHeight();
0290     }
0291 }
0292 
0293 void AssetParameterView::slotDeleteCurrentPreset()
0294 {
0295     QAction *ac = m_presetGroup->checkedAction();
0296     if (!ac) {
0297         return;
0298     }
0299     slotDeletePreset(ac->data().toString());
0300 }
0301 
0302 void AssetParameterView::slotDeletePreset(const QString &presetName)
0303 {
0304     if (presetName.isEmpty()) {
0305         return;
0306     }
0307     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
0308     if (dir.exists()) {
0309         const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId()));
0310         m_model->deletePreset(presetFile, presetName);
0311         Q_EMIT updatePresets();
0312     }
0313 }
0314 
0315 void AssetParameterView::slotUpdatePreset()
0316 {
0317     QAction *ac = m_presetGroup->checkedAction();
0318     if (!ac) {
0319         return;
0320     }
0321     slotSavePreset(ac->data().toString());
0322 }
0323 
0324 void AssetParameterView::slotSavePreset(QString presetName)
0325 {
0326     if (presetName.isEmpty()) {
0327         bool ok;
0328         presetName =
0329             QInputDialog::getText(this, i18nc("@title:window", "Enter Preset Name"), i18n("Enter the name of this preset:"), QLineEdit::Normal, QString(), &ok);
0330         if (!ok) return;
0331     }
0332     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
0333     if (!dir.exists()) {
0334         dir.mkpath(QStringLiteral("."));
0335     }
0336     const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId()));
0337     m_model->savePreset(presetFile, presetName);
0338     Q_EMIT updatePresets(presetName);
0339 }
0340 
0341 void AssetParameterView::slotLoadPreset()
0342 {
0343     auto *action = qobject_cast<QAction *>(sender());
0344     if (!action) {
0345         return;
0346     }
0347     const QString presetName = action->data().toString();
0348     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
0349     const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId()));
0350     const QVector<QPair<QString, QVariant>> params = m_model->loadPreset(presetFile, presetName);
0351     auto *command = new AssetUpdateCommand(m_model, params);
0352     pCore->pushUndo(command);
0353     Q_EMIT updatePresets(presetName);
0354 }
0355 
0356 QMenu *AssetParameterView::presetMenu()
0357 {
0358     return m_presetMenu;
0359 }