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 "effectstackview.hpp" 0007 #include "assets/assetlist/view/asseticonprovider.hpp" 0008 #include "assets/assetpanel.hpp" 0009 #include "assets/view/assetparameterview.hpp" 0010 #include "builtstack.hpp" 0011 #include "collapsibleeffectview.hpp" 0012 #include "core.h" 0013 #include "effects/effectsrepository.hpp" 0014 #include "effects/effectstack/model/effectitemmodel.hpp" 0015 #include "effects/effectstack/model/effectstackmodel.hpp" 0016 #include "kdenlivesettings.h" 0017 #include "monitor/monitor.h" 0018 0019 #include <QDir> 0020 #include <QDrag> 0021 #include <QFormLayout> 0022 #include <QDragEnterEvent> 0023 #include <QFontDatabase> 0024 #include <QInputDialog> 0025 #include <QMimeData> 0026 #include <QMutexLocker> 0027 #include <QPainter> 0028 #include <QScrollBar> 0029 #include <QTreeView> 0030 #include <QVBoxLayout> 0031 0032 #include "utils/KMessageBox_KdenliveCompat.h" 0033 #include <KMessageBox> 0034 #include <utility> 0035 0036 int dragRow = -1; 0037 0038 WidgetDelegate::WidgetDelegate(QObject *parent) 0039 : QStyledItemDelegate(parent) 0040 { 0041 } 0042 0043 QSize WidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0044 { 0045 QSize s = QStyledItemDelegate::sizeHint(option, index); 0046 if (m_height.contains(index)) { 0047 s.setHeight(m_height.value(index)); 0048 } 0049 return s; 0050 } 0051 0052 void WidgetDelegate::setHeight(const QModelIndex &index, int height) 0053 { 0054 m_height[index] = height; 0055 Q_EMIT sizeHintChanged(index); 0056 } 0057 0058 int WidgetDelegate::height(const QModelIndex &index) const 0059 { 0060 return m_height.value(index); 0061 } 0062 0063 void WidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0064 { 0065 QStyleOptionViewItem opt(option); 0066 initStyleOption(&opt, index); 0067 QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); 0068 if (index.row() == dragRow && !opt.rect.isNull()) { 0069 QPen pen(QPalette().highlight().color()); 0070 pen.setWidth(4); 0071 painter->setPen(pen); 0072 painter->drawLine(opt.rect.topLeft(), opt.rect.topRight()); 0073 } 0074 0075 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); 0076 } 0077 0078 EffectStackView::EffectStackView(AssetPanel *parent) 0079 : QWidget(parent) 0080 , m_model(nullptr) 0081 , m_thumbnailer(new AssetIconProvider(true, this)) 0082 { 0083 m_lay = new QVBoxLayout(this); 0084 m_lay->setContentsMargins(0, 0, 0, 0); 0085 m_lay->setSpacing(0); 0086 setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 0087 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); 0088 setAcceptDrops(true); 0089 /*m_builtStack = new BuiltStack(parent); 0090 m_builtStack->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); 0091 m_lay->addWidget(m_builtStack); 0092 m_builtStack->setVisible(KdenliveSettings::showbuiltstack());*/ 0093 m_effectsTree = new QTreeView(this); 0094 m_effectsTree->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); 0095 m_effectsTree->setHeaderHidden(true); 0096 m_effectsTree->setRootIsDecorated(false); 0097 m_effectsTree->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0098 QString style = QStringLiteral("QTreeView {border: none;}"); 0099 // m_effectsTree->viewport()->setAutoFillBackground(false); 0100 m_effectsTree->setStyleSheet(style); 0101 m_effectsTree->setVisible(!KdenliveSettings::showbuiltstack()); 0102 m_effectsTree->setItemDelegateForColumn(0, new WidgetDelegate(this)); 0103 m_lay->addWidget(m_effectsTree); 0104 m_lay->addStretch(10); 0105 0106 m_scrollTimer.setSingleShot(true); 0107 m_scrollTimer.setInterval(250); 0108 connect(&m_scrollTimer, &QTimer::timeout, this, &EffectStackView::checkScrollBar); 0109 0110 m_timerHeight.setSingleShot(true); 0111 m_timerHeight.setInterval(50); 0112 } 0113 0114 EffectStackView::~EffectStackView() 0115 { 0116 delete m_thumbnailer; 0117 } 0118 0119 void EffectStackView::dragEnterEvent(QDragEnterEvent *event) 0120 { 0121 if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) { 0122 if (event->source() == this) { 0123 event->setDropAction(Qt::MoveAction); 0124 } else { 0125 event->setDropAction(Qt::CopyAction); 0126 } 0127 event->setAccepted(true); 0128 } else { 0129 event->setAccepted(false); 0130 } 0131 } 0132 0133 void EffectStackView::dragLeaveEvent(QDragLeaveEvent *event) 0134 { 0135 event->accept(); 0136 dragRow = -1; 0137 repaint(); 0138 } 0139 0140 void EffectStackView::dragMoveEvent(QDragMoveEvent *event) 0141 { 0142 dragRow = m_model->rowCount(); 0143 for (int i = 0; i < m_model->rowCount(); i++) { 0144 auto item = m_model->getEffectStackRow(i); 0145 if (item->childCount() > 0) { 0146 // TODO: group 0147 continue; 0148 } 0149 std::shared_ptr<EffectItemModel> eff = std::static_pointer_cast<EffectItemModel>(item); 0150 QModelIndex ix = m_model->getIndexFromItem(eff); 0151 QWidget *w = m_effectsTree->indexWidget(ix); 0152 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0153 if (w && w->geometry().contains(event->pos())) { 0154 #else 0155 if (w && w->geometry().contains(event->position().toPoint())) { 0156 #endif 0157 if (event->source() == this) { 0158 QString sourceData = event->mimeData()->data(QStringLiteral("kdenlive/effectsource")); 0159 int oldRow = sourceData.section(QLatin1Char(','), 2, 2).toInt(); 0160 if (i == oldRow + 1) { 0161 dragRow = -1; 0162 break; 0163 } 0164 } 0165 dragRow = i; 0166 break; 0167 } 0168 } 0169 if (dragRow == m_model->rowCount() && event->source() == this) { 0170 QString sourceData = event->mimeData()->data(QStringLiteral("kdenlive/effectsource")); 0171 int oldRow = sourceData.section(QLatin1Char(','), 2, 2).toInt(); 0172 if (dragRow == oldRow + 1) { 0173 dragRow = -1; 0174 } 0175 } 0176 repaint(); 0177 } 0178 0179 void EffectStackView::dropEvent(QDropEvent *event) 0180 { 0181 qDebug() << ":::: DROP BEGIN EVENT...."; 0182 if (dragRow < 0) { 0183 return; 0184 } 0185 QString effectId = event->mimeData()->data(QStringLiteral("kdenlive/effect")); 0186 if (event->source() == this) { 0187 QString sourceData = event->mimeData()->data(QStringLiteral("kdenlive/effectsource")); 0188 int oldRow = sourceData.section(QLatin1Char(','), 2, 2).toInt(); 0189 qDebug() << "// MOVING EFFECT FROM : " << oldRow << " TO " << dragRow; 0190 if (dragRow == oldRow || (dragRow == m_model->rowCount() && oldRow == dragRow - 1)) { 0191 return; 0192 } 0193 QMetaObject::invokeMethod(m_model.get(), "moveEffectByRow", Qt::QueuedConnection, Q_ARG(int, dragRow), Q_ARG(int, oldRow)); 0194 } else { 0195 bool added = false; 0196 if (dragRow < m_model->rowCount()) { 0197 if (m_model->appendEffect(effectId) && m_model->rowCount() > 0) { 0198 added = true; 0199 m_model->moveEffect(dragRow, m_model->getEffectStackRow(m_model->rowCount() - 1)); 0200 } 0201 } else { 0202 if (m_model->appendEffect(effectId) && m_model->rowCount() > 0) { 0203 added = true; 0204 m_model->setActiveEffect(m_model->rowCount() - 1); 0205 } 0206 } 0207 if (added) { 0208 m_scrollTimer.start(); 0209 } 0210 } 0211 dragRow = -1; 0212 event->acceptProposedAction(); 0213 qDebug() << ":::: DROP END EVENT...."; 0214 } 0215 0216 void EffectStackView::paintEvent(QPaintEvent *event) 0217 { 0218 if (dragRow == m_model->rowCount()) { 0219 QWidget::paintEvent(event); 0220 QPainter p(this); 0221 QPen pen(palette().highlight().color()); 0222 pen.setWidth(4); 0223 p.setPen(pen); 0224 p.drawLine(0, m_effectsTree->height(), width(), m_effectsTree->height()); 0225 } 0226 } 0227 0228 void EffectStackView::setModel(std::shared_ptr<EffectStackModel> model, const QSize frameSize) 0229 { 0230 qDebug() << "MUTEX LOCK!!!!!!!!!!!! setmodel"; 0231 m_mutex.lock(); 0232 QItemSelectionModel *m = m_effectsTree->selectionModel(); 0233 unsetModel(false); 0234 m_effectsTree->setModel(nullptr); 0235 m_model.reset(); 0236 if (m) { 0237 delete m; 0238 } 0239 m_effectsTree->setFixedHeight(0); 0240 m_model = std::move(model); 0241 m_sourceFrameSize = frameSize; 0242 m_effectsTree->setModel(m_model.get()); 0243 m_effectsTree->setColumnHidden(1, true); 0244 m_effectsTree->setAcceptDrops(true); 0245 m_effectsTree->setDragDropMode(QAbstractItemView::DragDrop); 0246 m_effectsTree->setDragEnabled(true); 0247 m_effectsTree->setUniformRowHeights(false); 0248 m_mutex.unlock(); 0249 qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! setmodel"; 0250 loadEffects(); 0251 m_scrollTimer.start(); 0252 connect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh); 0253 connect(m_model.get(), &EffectStackModel::enabledStateChanged, this, &EffectStackView::changeEnabledState); 0254 connect(m_model.get(), &EffectStackModel::currentChanged, this, &EffectStackView::activateEffect, Qt::DirectConnection); 0255 connect(this, &EffectStackView::removeCurrentEffect, m_model.get(), &EffectStackModel::removeCurrentEffect); 0256 // m_builtStack->setModel(model, stackOwner()); 0257 } 0258 0259 void EffectStackView::activateEffect(const QModelIndex &ix, bool active) 0260 { 0261 m_effectsTree->setCurrentIndex(ix); 0262 auto *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(ix)); 0263 if (w) { 0264 w->slotActivateEffect(active); 0265 } 0266 } 0267 0268 void EffectStackView::changeEnabledState() 0269 { 0270 int max = m_model->rowCount(); 0271 int currentActive = m_model->getActiveEffect(); 0272 if (currentActive < max && currentActive > -1) { 0273 auto item = m_model->getEffectStackRow(currentActive); 0274 QModelIndex ix = m_model->getIndexFromItem(item); 0275 auto *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(ix)); 0276 w->updateScene(); 0277 } 0278 Q_EMIT updateEnabledState(); 0279 } 0280 0281 void EffectStackView::loadEffects() 0282 { 0283 // QMutexLocker lock(&m_mutex); 0284 int max = m_model->rowCount(); 0285 qDebug() << "MUTEX LOCK!!!!!!!!!!!! loadEffects COUNT: " << max; 0286 if (max == 0) { 0287 // blank stack 0288 ObjectId item = m_model->getOwnerId(); 0289 pCore->getMonitor(item.type == KdenliveObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor) 0290 ->slotShowEffectScene(MonitorSceneDefault); 0291 updateTreeHeight(); 0292 return; 0293 } 0294 int active = qBound(0, m_model->getActiveEffect(), max - 1); 0295 bool hasLift = false; 0296 QModelIndex activeIndex; 0297 connect(&m_timerHeight, &QTimer::timeout, this, &EffectStackView::updateTreeHeight, Qt::UniqueConnection); 0298 for (int i = 0; i < max; i++) { 0299 std::shared_ptr<AbstractEffectItem> item = m_model->getEffectStackRow(i); 0300 if (item->childCount() > 0) { 0301 // group, create sub stack 0302 continue; 0303 } 0304 std::shared_ptr<EffectItemModel> effectModel = std::static_pointer_cast<EffectItemModel>(item); 0305 CollapsibleEffectView *view = nullptr; 0306 // We need to rebuild the effect view 0307 if (effectModel->getAssetId() == QLatin1String("lift_gamma_gain")) { 0308 hasLift = true; 0309 } 0310 const QString assetName = EffectsRepository::get()->getName(effectModel->getAssetId()); 0311 view = new CollapsibleEffectView(assetName, effectModel, m_sourceFrameSize, this); 0312 connect(view, &CollapsibleEffectView::deleteEffect, m_model.get(), &EffectStackModel::removeEffect); 0313 connect(view, &CollapsibleEffectView::moveEffect, m_model.get(), &EffectStackModel::moveEffect); 0314 connect(view, &CollapsibleEffectView::reloadEffect, this, &EffectStackView::reloadEffect); 0315 connect(view, &CollapsibleEffectView::switchHeight, this, &EffectStackView::slotAdjustDelegate, Qt::DirectConnection); 0316 connect(view, &CollapsibleEffectView::startDrag, this, &EffectStackView::slotStartDrag); 0317 connect(view, &CollapsibleEffectView::activateEffect, this, [=](int row) { m_model->setActiveEffect(row); }); 0318 connect(view, &CollapsibleEffectView::createGroup, m_model.get(), &EffectStackModel::slotCreateGroup); 0319 connect(view, &CollapsibleEffectView::showEffectZone, pCore.get(), &Core::showEffectZone); 0320 connect(this, &EffectStackView::blockWheelEvent, view, &CollapsibleEffectView::blockWheelEvent); 0321 connect(view, &CollapsibleEffectView::seekToPos, this, [this](int pos) { 0322 // at this point, the effects returns a pos relative to the clip. We need to convert it to a global time 0323 int clipIn = pCore->getItemPosition(m_model->getOwnerId()); 0324 Q_EMIT seekToPos(pos + clipIn); 0325 }); 0326 connect(this, &EffectStackView::switchCollapsedView, view, &CollapsibleEffectView::switchCollapsed); 0327 0328 connect(pCore.get(), &Core::updateEffectZone, view, [=](const QPoint p, bool withUndo) { 0329 // Update current effect zone 0330 if (view->isActive()) { 0331 view->updateInOut({p.x(), p.y()}, withUndo); 0332 } 0333 }); 0334 QModelIndex ix = m_model->getIndexFromItem(effectModel); 0335 if (active == i) { 0336 effectModel->setActive(true); 0337 activeIndex = ix; 0338 } 0339 m_effectsTree->setIndexWidget(ix, view); 0340 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0341 auto *del = static_cast<WidgetDelegate *>(m_effectsTree->itemDelegate(ix)); 0342 #else 0343 auto *del = static_cast<WidgetDelegate *>(m_effectsTree->itemDelegateForIndex(ix)); 0344 #endif 0345 del->setHeight(ix, view->height()); 0346 view->buttonUp->setEnabled(i > 0); 0347 view->buttonDown->setEnabled(i < max - 1); 0348 } 0349 if (!hasLift) { 0350 updateTreeHeight(); 0351 } 0352 if (activeIndex.isValid()) { 0353 m_effectsTree->setCurrentIndex(activeIndex); 0354 auto *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(activeIndex)); 0355 if (w) { 0356 w->slotActivateEffect(true); 0357 } 0358 } 0359 if (hasLift) { 0360 // Some effects have a complex timed layout, so we need to wait a bit before getting the correct position for the effect 0361 QTimer::singleShot(100, this, &EffectStackView::slotFocusEffect); 0362 } else { 0363 slotFocusEffect(); 0364 } 0365 qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! loadEffects"; 0366 } 0367 0368 void EffectStackView::updateTreeHeight() 0369 { 0370 // For some reason, the treeview height does not update correctly, so enforce it 0371 QMutexLocker lk(&m_mutex); 0372 if (!m_model) { 0373 return; 0374 } 0375 int totalHeight = 0; 0376 for (int j = 0; j < m_model->rowCount(); j++) { 0377 std::shared_ptr<AbstractEffectItem> item2 = m_model->getEffectStackRow(j); 0378 std::shared_ptr<EffectItemModel> eff = std::static_pointer_cast<EffectItemModel>(item2); 0379 QModelIndex idx = m_model->getIndexFromItem(eff); 0380 auto w = m_effectsTree->indexWidget(idx); 0381 if (w) { 0382 totalHeight += w->height(); 0383 } 0384 } 0385 if (totalHeight != m_effectsTree->height()) { 0386 m_effectsTree->setFixedHeight(totalHeight); 0387 m_scrollTimer.start(); 0388 } 0389 } 0390 0391 void EffectStackView::slotStartDrag(const QPixmap pix, const QString assetId, ObjectId sourceObject, int row) 0392 { 0393 auto *drag = new QDrag(this); 0394 drag->setPixmap(pix); 0395 auto *mime = new QMimeData; 0396 mime->setData(QStringLiteral("kdenlive/effect"), assetId.toUtf8()); 0397 // TODO this will break if source effect is not on the stack of a timeline clip 0398 QByteArray effectSource; 0399 effectSource += QString::number(int(sourceObject.type)).toUtf8(); 0400 effectSource += ','; 0401 effectSource += QString::number(int(sourceObject.itemId)).toUtf8(); 0402 effectSource += ','; 0403 effectSource += QString::number(row).toUtf8(); 0404 effectSource += ','; 0405 if (sourceObject.type == KdenliveObjectType::BinClip) { 0406 effectSource += QByteArray(); 0407 } else { 0408 // Keep a reference to the timeline model 0409 effectSource += pCore->currentTimelineId().toString().toUtf8(); 0410 } 0411 mime->setData(QStringLiteral("kdenlive/effectsource"), effectSource); 0412 0413 // Assign ownership of the QMimeData object to the QDrag object. 0414 drag->setMimeData(mime); 0415 // Start the drag and drop operation 0416 drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction); 0417 } 0418 0419 void EffectStackView::slotAdjustDelegate(const std::shared_ptr<EffectItemModel> &effectModel, int newHeight) 0420 { 0421 if (!m_model) { 0422 return; 0423 } 0424 QModelIndex ix = m_model->getIndexFromItem(effectModel); 0425 if (ix.isValid()) { 0426 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0427 auto *del = static_cast<WidgetDelegate *>(m_effectsTree->itemDelegate(ix)); 0428 #else 0429 auto *del = static_cast<WidgetDelegate *>(m_effectsTree->itemDelegateForIndex(ix)); 0430 #endif 0431 if (del) { 0432 del->setHeight(ix, newHeight); 0433 m_timerHeight.start(); 0434 } 0435 } 0436 } 0437 0438 void EffectStackView::resizeEvent(QResizeEvent *event) 0439 { 0440 QWidget::resizeEvent(event); 0441 m_scrollTimer.start(); 0442 } 0443 0444 void EffectStackView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) 0445 { 0446 Q_UNUSED(roles) 0447 if (!topLeft.isValid() || !bottomRight.isValid()) { 0448 loadEffects(); 0449 return; 0450 } 0451 for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { 0452 for (int j = topLeft.column(); j <= bottomRight.column(); ++j) { 0453 CollapsibleEffectView *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(m_model->index(i, j, topLeft.parent()))); 0454 if (w) { 0455 Q_EMIT w->refresh(); 0456 } 0457 } 0458 } 0459 } 0460 0461 void EffectStackView::unsetModel(bool reset) 0462 { 0463 // Release ownership of smart pointer 0464 Kdenlive::MonitorId id = Kdenlive::NoMonitor; 0465 if (m_model) { 0466 ObjectId item = m_model->getOwnerId(); 0467 pCore->showEffectZone(item, {0, 0}, false); 0468 id = item.type == KdenliveObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor; 0469 disconnect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh); 0470 disconnect(m_model.get(), &EffectStackModel::enabledStateChanged, this, &EffectStackView::changeEnabledState); 0471 disconnect(this, &EffectStackView::removeCurrentEffect, m_model.get(), &EffectStackModel::removeCurrentEffect); 0472 disconnect(m_model.get(), &EffectStackModel::currentChanged, this, &EffectStackView::activateEffect); 0473 disconnect(&m_timerHeight, &QTimer::timeout, this, &EffectStackView::updateTreeHeight); 0474 Q_EMIT pCore->disconnectEffectStack(); 0475 if (reset) { 0476 QMutexLocker lock(&m_mutex); 0477 m_model.reset(); 0478 m_effectsTree->setModel(nullptr); 0479 } 0480 pCore->getMonitor(id)->slotShowEffectScene(MonitorSceneDefault); 0481 } 0482 } 0483 0484 ObjectId EffectStackView::stackOwner() const 0485 { 0486 if (m_model) { 0487 return m_model->getOwnerId(); 0488 } 0489 return ObjectId(); 0490 } 0491 0492 bool EffectStackView::addEffect(const QString &effectId) 0493 { 0494 if (m_model) { 0495 return m_model->appendEffect(effectId, true); 0496 } 0497 return false; 0498 } 0499 0500 bool EffectStackView::isEmpty() const 0501 { 0502 return m_model == nullptr ? true : m_model->rowCount() == 0; 0503 } 0504 0505 void EffectStackView::enableStack(bool enable) 0506 { 0507 if (m_model) { 0508 m_model->setEffectStackEnabled(enable); 0509 } 0510 } 0511 0512 bool EffectStackView::isStackEnabled() const 0513 { 0514 if (m_model) { 0515 return m_model->isStackEnabled(); 0516 } 0517 return false; 0518 } 0519 0520 void EffectStackView::switchCollapsed() 0521 { 0522 if (m_model) { 0523 int max = m_model->rowCount(); 0524 int active = qBound(0, m_model->getActiveEffect(), max - 1); 0525 Q_EMIT switchCollapsedView(active); 0526 } 0527 } 0528 0529 void EffectStackView::slotFocusEffect() 0530 { 0531 Q_EMIT scrollView(m_effectsTree->visualRect(m_effectsTree->currentIndex())); 0532 } 0533 0534 void EffectStackView::slotSaveStack() 0535 { 0536 if (m_model->rowCount() == 1) { 0537 int currentActive = m_model->getActiveEffect(); 0538 if (currentActive > -1) { 0539 auto item = m_model->getEffectStackRow(currentActive); 0540 QModelIndex ix = m_model->getIndexFromItem(item); 0541 auto *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(ix)); 0542 w->slotSaveEffect(); 0543 return; 0544 } 0545 } 0546 if (m_model->rowCount() <= 1) { 0547 KMessageBox::error(this, i18n("No effect selected.")); 0548 return; 0549 } 0550 QDialog dialog(this); 0551 QFormLayout form(&dialog); 0552 0553 dialog.setWindowTitle(i18nc("@title:window", "Save current Effect Stack")); 0554 0555 auto *stackName = new QLineEdit(&dialog); 0556 auto *stackDescription = new QTextEdit(&dialog); 0557 0558 form.addRow(i18n("Name for saved stack:"), stackName); 0559 form.addRow(i18n("Description:"), stackDescription); 0560 0561 0562 QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); 0563 form.addRow(&buttonBox); 0564 0565 QObject::connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); 0566 QObject::connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); 0567 0568 if (dialog.exec() == QDialog::Accepted) { 0569 QString name = stackName->text(); 0570 QString description = stackDescription->toPlainText(); 0571 if (name.trimmed().isEmpty()) { 0572 KMessageBox::error(this, i18n("No name provided, effect stack not saved.")); 0573 return; 0574 } 0575 0576 QString effectfilename = name + QStringLiteral(".xml"); 0577 0578 if (description.trimmed().isEmpty()) { 0579 if (KMessageBox::questionTwoActions(this, i18n("No description provided. \nSave effect with no description?"), {}, KStandardGuiItem::save(), 0580 KStandardGuiItem::dontSave()) == KMessageBox::SecondaryAction) { 0581 return; 0582 } 0583 } 0584 0585 QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/")); 0586 if (!dir.exists()) { 0587 dir.mkpath(QStringLiteral(".")); 0588 } 0589 0590 if (dir.exists(effectfilename)) { 0591 if (KMessageBox::questionTwoActions(this, i18n("File %1 already exists.\nDo you want to overwrite it?", effectfilename), {}, 0592 KStandardGuiItem::overwrite(), KStandardGuiItem::cancel()) == KMessageBox::SecondaryAction) { 0593 return; 0594 } 0595 } 0596 0597 QDomDocument doc; 0598 0599 QDomElement effect = doc.createElement(QStringLiteral("effectgroup")); 0600 effect.setAttribute(QStringLiteral("id"), name); 0601 0602 QDomElement describtionNode = doc.createElement(QString("description")); 0603 QDomText descriptionText = doc.createTextNode(description); 0604 describtionNode.appendChild(descriptionText); 0605 0606 effect.appendChild(describtionNode); 0607 effect.setAttribute(QStringLiteral("description"), description); 0608 0609 auto item = m_model->getEffectStackRow(0); 0610 if (item->isAudio()) { 0611 effect.setAttribute(QStringLiteral("type"), QStringLiteral("customAudio")); 0612 } 0613 effect.setAttribute(QStringLiteral("parentIn"), pCore->getItemIn(m_model->getOwnerId())); 0614 doc.appendChild(effect); 0615 for (int i = 0; i <= m_model->rowCount(); ++i) { 0616 CollapsibleEffectView *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(m_model->index(i, 0, QModelIndex()))); 0617 if (w) { 0618 effect.appendChild(doc.importNode(w->toXml().documentElement(), true)); 0619 } 0620 } 0621 QFile file(dir.absoluteFilePath(effectfilename)); 0622 if (file.open(QFile::WriteOnly | QFile::Truncate)) { 0623 QTextStream out(&file); 0624 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0625 out.setCodec("UTF-8"); 0626 #endif 0627 out << doc.toString(); 0628 } else { 0629 KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", file.fileName())); 0630 } 0631 file.close(); 0632 Q_EMIT reloadEffect(dir.absoluteFilePath(effectfilename)); 0633 } 0634 } 0635 0636 /* 0637 void EffectStackView::switchBuiltStack(bool show) 0638 { 0639 m_builtStack->setVisible(show); 0640 m_effectsTree->setVisible(!show); 0641 KdenliveSettings::setShowbuiltstack(show); 0642 } 0643 */ 0644 0645 void EffectStackView::slotGoToKeyframe(bool next) 0646 { 0647 int max = m_model->rowCount(); 0648 int currentActive = m_model->getActiveEffect(); 0649 if (currentActive < max && currentActive > -1) { 0650 auto item = m_model->getEffectStackRow(currentActive); 0651 QModelIndex ix = m_model->getIndexFromItem(item); 0652 auto *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(ix)); 0653 if (next) { 0654 w->slotNextKeyframe(); 0655 } else { 0656 w->slotPreviousKeyframe(); 0657 } 0658 } 0659 } 0660 0661 void EffectStackView::addRemoveKeyframe() 0662 { 0663 int max = m_model->rowCount(); 0664 int currentActive = m_model->getActiveEffect(); 0665 if (currentActive < max && currentActive > -1) { 0666 auto item = m_model->getEffectStackRow(currentActive); 0667 QModelIndex ix = m_model->getIndexFromItem(item); 0668 auto *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(ix)); 0669 w->addRemoveKeyframe(); 0670 } 0671 } 0672 0673 void EffectStackView::sendStandardCommand(int command) 0674 { 0675 int max = m_model->rowCount(); 0676 int currentActive = m_model->getActiveEffect(); 0677 if (currentActive < max && currentActive > -1) { 0678 auto item = m_model->getEffectStackRow(currentActive); 0679 QModelIndex ix = m_model->getIndexFromItem(item); 0680 auto *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(ix)); 0681 w->sendStandardCommand(command); 0682 } 0683 }