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 }