diff --git a/src/abstractmodel/abstracttreemodel.cpp b/src/abstractmodel/abstracttreemodel.cpp index df060e1a1..c3a07b777 100644 --- a/src/abstractmodel/abstracttreemodel.cpp +++ b/src/abstractmodel/abstracttreemodel.cpp @@ -1,349 +1,352 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "abstracttreemodel.hpp" #include "treeitem.hpp" #include #include #include #include int AbstractTreeModel::currentTreeId = 0; AbstractTreeModel::AbstractTreeModel(QObject *parent) : QAbstractItemModel(parent) { } std::shared_ptr AbstractTreeModel::construct(QObject *parent) { std::shared_ptr self(new AbstractTreeModel(parent)); self->rootItem = TreeItem::construct(QList(), self, true); return self; } AbstractTreeModel::~AbstractTreeModel() { m_allItems.clear(); rootItem.reset(); } int AbstractTreeModel::columnCount(const QModelIndex &parent) const { if (!parent.isValid()) return rootItem->columnCount(); const auto id = (int)parent.internalId(); auto item = getItemById(id); return item->columnCount(); } QVariant AbstractTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (role != Qt::DisplayRole) { return QVariant(); } auto item = getItemById((int)index.internalId()); return item->dataColumn(index.column()); } Qt::ItemFlags AbstractTreeModel::flags(const QModelIndex &index) const { const auto flags = QAbstractItemModel::flags(index); if (index.isValid()) { auto item = getItemById((int)index.internalId()); if (item->depth() == 1) { return flags & ~Qt::ItemIsSelectable; } } return flags; } QVariant AbstractTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return rootItem->dataColumn(section); return QVariant(); } QModelIndex AbstractTreeModel::index(int row, int column, const QModelIndex &parent) const { std::shared_ptr parentItem; if (!parent.isValid()) parentItem = rootItem; else parentItem = getItemById((int)parent.internalId()); if (row >= parentItem->childCount()) return QModelIndex(); std::shared_ptr childItem = parentItem->child(row); if (childItem) return createIndex(row, column, quintptr(childItem->getId())); return {}; } QModelIndex AbstractTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return {}; std::shared_ptr childItem = getItemById((int)index.internalId()); std::shared_ptr parentItem = childItem->parentItem().lock(); Q_ASSERT(parentItem); if (parentItem == rootItem) return QModelIndex(); return createIndex(parentItem->row(), 0, quintptr(parentItem->getId())); } int AbstractTreeModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) return 0; std::shared_ptr parentItem; if (!parent.isValid()) parentItem = rootItem; else parentItem = getItemById((int)parent.internalId()); return parentItem->childCount(); } QModelIndex AbstractTreeModel::getIndexFromItem(const std::shared_ptr &item) const { if (item == rootItem) { - return {}; + return QModelIndex(); + } + if (auto ptr = item->parentItem().lock()) { + auto parentIndex = getIndexFromItem(ptr); + return index(item->row(), 0, parentIndex); } - auto parentIndex = getIndexFromItem(item->parentItem().lock()); - return index(item->row(), 0, parentIndex); + return QModelIndex(); } QModelIndex AbstractTreeModel::getIndexFromId(int id) const { if (id == rootItem->getId()) { return QModelIndex(); } Q_ASSERT(m_allItems.count(id) > 0); if (auto ptr = m_allItems.at(id).lock()) return getIndexFromItem(ptr); Q_ASSERT(false); return {}; } void AbstractTreeModel::notifyRowAboutToAppend(const std::shared_ptr &item) { auto index = getIndexFromItem(item); beginInsertRows(index, item->childCount(), item->childCount()); } void AbstractTreeModel::notifyRowAppended(const std::shared_ptr &row) { Q_UNUSED(row); endInsertRows(); } void AbstractTreeModel::notifyRowAboutToDelete(std::shared_ptr item, int row) { auto index = getIndexFromItem(item); beginRemoveRows(index, row, row); } void AbstractTreeModel::notifyRowDeleted() { endRemoveRows(); } // static int AbstractTreeModel::getNextId() { return currentTreeId++; } void AbstractTreeModel::registerItem(const std::shared_ptr &item) { int id = item->getId(); Q_ASSERT(m_allItems.count(id) == 0); m_allItems[id] = item; } void AbstractTreeModel::deregisterItem(int id, TreeItem *item) { Q_UNUSED(item); Q_ASSERT(m_allItems.count(id) > 0); m_allItems.erase(id); } std::shared_ptr AbstractTreeModel::getItemById(int id) const { if (id == rootItem->getId()) { return rootItem; } Q_ASSERT(m_allItems.count(id) > 0); return m_allItems.at(id).lock(); } std::shared_ptr AbstractTreeModel::getRoot() const { return rootItem; } bool AbstractTreeModel::checkConsistency() { // first check that the root is all good if (!rootItem || !rootItem->m_isRoot || !rootItem->isInModel() || m_allItems.count(rootItem->getId()) == 0) { qDebug() << !rootItem->m_isRoot << !rootItem->isInModel() << (m_allItems.count(rootItem->getId()) == 0); qDebug() << "ERROR: Model is not valid because root is not properly constructed"; return false; } // Then we traverse the tree from the root, checking the infos on the way std::unordered_set seenIDs; std::queue>> queue; // store (id, (depth, parentId)) queue.push({rootItem->getId(), {0, rootItem->getId()}}); while (!queue.empty()) { auto current = queue.front(); int currentId = current.first, currentDepth = current.second.first; int parentId = current.second.second; queue.pop(); if (seenIDs.count(currentId) != 0) { qDebug() << "ERROR: Invalid tree: Id found twice." << "It either a cycle or a clash in id attribution"; return false; } if (m_allItems.count(currentId) == 0) { qDebug() << "ERROR: Invalid tree: Id not found. Item is not registered"; return false; } auto currentItem = m_allItems[currentId].lock(); if (currentItem->depth() != currentDepth) { qDebug() << "ERROR: Invalid tree: invalid depth info found"; return false; } if (!currentItem->isInModel()) { qDebug() << "ERROR: Invalid tree: item thinks it is not in a model"; return false; } if (currentId != rootItem->getId()) { if ((currentDepth == 0 || currentItem->m_isRoot)) { qDebug() << "ERROR: Invalid tree: duplicate root"; return false; } if (auto ptr = currentItem->parentItem().lock()) { if (ptr->getId() != parentId || ptr->child(currentItem->row())->getId() != currentItem->getId()) { qDebug() << "ERROR: Invalid tree: invalid parent link"; return false; } } else { qDebug() << "ERROR: Invalid tree: invalid parent"; return false; } } // propagate to children int i = 0; for (const auto &child : currentItem->m_childItems) { if (currentItem->child(i) != child) { qDebug() << "ERROR: Invalid tree: invalid child ordering"; return false; } queue.push({child->getId(), {currentDepth + 1, currentId}}); i++; } } return true; } Fun AbstractTreeModel::addItem_lambda(const std::shared_ptr &new_item, int parentId) { return [this, new_item, parentId]() { /* Insertion is simply setting the parent of the item.*/ std::shared_ptr parent; if (parentId != -1) { parent = getItemById(parentId); if (!parent) { Q_ASSERT(parent); return false; } } return new_item->changeParent(parent); }; } Fun AbstractTreeModel::removeItem_lambda(int id) { return [this, id]() { /* Deletion simply deregister clip and remove it from parent. The actual object is not actually deleted, because a shared_pointer to it is captured by the reverse operation. Actual deletions occurs when the undo object is destroyed. */ auto item = m_allItems[id].lock(); Q_ASSERT(item); if (!item) { return false; } auto parent = item->parentItem().lock(); parent->removeChild(item); return true; }; } Fun AbstractTreeModel::moveItem_lambda(int id, int destRow, bool force) { Fun lambda = []() { return true; }; std::vector> oldStack; auto item = getItemById(id); if (!force && item->row() == destRow) { // nothing to do return lambda; } if (auto parent = item->parentItem().lock()) { if (destRow > parent->childCount() || destRow < 0) { return []() { return false; }; } int parentId = parent->getId(); // remove the element to move oldStack.push_back(item); Fun oper = removeItem_lambda(id); PUSH_LAMBDA(oper, lambda); // remove the tail of the stack for (int i = destRow; i < parent->childCount(); ++i) { auto current = parent->child(i); if (current->getId() != id) { oldStack.push_back(current); oper = removeItem_lambda(current->getId()); PUSH_LAMBDA(oper, lambda); } } // insert back in order for (const auto &elem : oldStack) { oper = addItem_lambda(elem, parentId); PUSH_LAMBDA(oper, lambda); } return lambda; } return []() { return false; }; } diff --git a/src/effects/effectstack/view/effectstackview.cpp b/src/effects/effectstack/view/effectstackview.cpp index dc33830e9..c7a5da31f 100644 --- a/src/effects/effectstack/view/effectstackview.cpp +++ b/src/effects/effectstack/view/effectstackview.cpp @@ -1,408 +1,410 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "effectstackview.hpp" #include "assets/assetlist/view/qmltypes/asseticonprovider.hpp" #include "assets/assetpanel.hpp" #include "assets/view/assetparameterview.hpp" #include "builtstack.hpp" #include "collapsibleeffectview.hpp" #include "core.h" #include "effects/effectstack/model/effectitemmodel.hpp" #include "effects/effectstack/model/effectstackmodel.hpp" #include "kdenlivesettings.h" #include "monitor/monitor.h" #include #include #include #include #include #include #include #include #include WidgetDelegate::WidgetDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QSize WidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize s = QStyledItemDelegate::sizeHint(option, index); if (m_height.contains(index)) { s.setHeight(m_height.value(index)); } return s; } void WidgetDelegate::setHeight(const QModelIndex &index, int height) { m_height[index] = height; emit sizeHintChanged(index); } int WidgetDelegate::height(const QModelIndex &index) const { return m_height.value(index); } void WidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt(option); initStyleOption(&opt, index); QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); } EffectStackView::EffectStackView(AssetPanel *parent) : QWidget(parent) , m_model(nullptr) , m_thumbnailer(new AssetIconProvider(true)) { m_lay = new QVBoxLayout(this); m_lay->setContentsMargins(0, 0, 0, 0); m_lay->setSpacing(0); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); setAcceptDrops(true); /*m_builtStack = new BuiltStack(parent); m_builtStack->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_lay->addWidget(m_builtStack); m_builtStack->setVisible(KdenliveSettings::showbuiltstack());*/ m_effectsTree = new QTreeView(this); m_effectsTree->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); m_effectsTree->setHeaderHidden(true); m_effectsTree->setRootIsDecorated(false); QString style = QStringLiteral("QTreeView {border: none;}"); // m_effectsTree->viewport()->setAutoFillBackground(false); m_effectsTree->setStyleSheet(style); m_effectsTree->setVisible(!KdenliveSettings::showbuiltstack()); m_lay->addWidget(m_effectsTree); m_lay->addStretch(10); m_scrollTimer.setSingleShot(true); m_scrollTimer.setInterval(250); connect(&m_scrollTimer, &QTimer::timeout, this, &EffectStackView::checkScrollBar); } EffectStackView::~EffectStackView() { delete m_thumbnailer; } void EffectStackView::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) { if (event->source() == this) { event->setDropAction(Qt::MoveAction); } else { event->setDropAction(Qt::CopyAction); } event->setAccepted(true); } else { event->setAccepted(false); } } void EffectStackView::dropEvent(QDropEvent *event) { event->accept(); QString effectId = event->mimeData()->data(QStringLiteral("kdenlive/effect")); int row = m_model->rowCount(); for (int i = 0; i < m_model->rowCount(); i++) { auto item = m_model->getEffectStackRow(i); if (item->childCount() > 0) { // TODO: group continue; } std::shared_ptr eff = std::static_pointer_cast(item); QModelIndex ix = m_model->getIndexFromItem(eff); QWidget *w = m_effectsTree->indexWidget(ix); if (w && w->geometry().contains(event->pos())) { qDebug() << "// DROPPED ON EFF: " << eff->getAssetId(); row = i; break; } } if (event->source() == this) { QString sourceData = event->mimeData()->data(QStringLiteral("kdenlive/effectsource")); int oldRow = sourceData.section(QLatin1Char('-'), 2, 2).toInt(); qDebug() << "// MOVING EFFECT FROM : " << oldRow << " TO " << row; if (row == oldRow || (row == m_model->rowCount() && oldRow == row - 1)) { return; } m_model->moveEffect(row, m_model->getEffectStackRow(oldRow)); } else { bool added = false; if (row < m_model->rowCount()) { if (m_model->appendEffect(effectId)) { added = true; m_model->moveEffect(row, m_model->getEffectStackRow(m_model->rowCount() - 1)); } } else { if (m_model->appendEffect(effectId)) { added = true; std::shared_ptr item = m_model->getEffectStackRow(m_model->rowCount() - 1); if (item) { slotActivateEffect(std::static_pointer_cast(item)); } } } if (!added) { pCore->displayMessage(i18n("Cannot add effect to clip"), InformationMessage); } else { m_scrollTimer.start(); } } } void EffectStackView::setModel(std::shared_ptr model, const QSize frameSize) { qDebug() << "MUTEX LOCK!!!!!!!!!!!! setmodel"; m_mutex.lock(); unsetModel(false); m_model = std::move(model); m_sourceFrameSize = frameSize; m_effectsTree->setModel(m_model.get()); m_effectsTree->setItemDelegateForColumn(0, new WidgetDelegate(this)); m_effectsTree->setColumnHidden(1, true); m_effectsTree->setAcceptDrops(true); m_effectsTree->setDragDropMode(QAbstractItemView::DragDrop); m_effectsTree->setDragEnabled(true); m_effectsTree->setUniformRowHeights(false); m_mutex.unlock(); qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! setmodel"; loadEffects(); m_scrollTimer.start(); connect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh); connect(m_model.get(), &EffectStackModel::enabledStateChanged, this, &EffectStackView::updateEnabledState); connect(this, &EffectStackView::removeCurrentEffect, m_model.get(), &EffectStackModel::removeCurrentEffect); // m_builtStack->setModel(model, stackOwner()); } void EffectStackView::loadEffects() { qDebug() << "MUTEX LOCK!!!!!!!!!!!! loadEffects: "; //QMutexLocker lock(&m_mutex); int max = m_model->rowCount(); if (max == 0) { // blank stack ObjectId item = m_model->getOwnerId(); pCore->getMonitor(item.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor)->slotShowEffectScene(MonitorSceneDefault); return; } int active = qBound(0, m_model->getActiveEffect(), max - 1); for (int i = 0; i < max; i++) { std::shared_ptr item = m_model->getEffectStackRow(i); QSize size; if (item->childCount() > 0) { // group, create sub stack continue; } std::shared_ptr effectModel = std::static_pointer_cast(item); CollapsibleEffectView *view = nullptr; // We need to rebuild the effect view QImage effectIcon = m_thumbnailer->requestImage(effectModel->getAssetId(), &size, QSize(QStyle::PM_SmallIconSize, QStyle::PM_SmallIconSize)); view = new CollapsibleEffectView(effectModel, m_sourceFrameSize, effectIcon, this); connect(view, &CollapsibleEffectView::deleteEffect, m_model.get(), &EffectStackModel::removeEffect); connect(view, &CollapsibleEffectView::moveEffect, m_model.get(), &EffectStackModel::moveEffect); connect(view, &CollapsibleEffectView::reloadEffect, this, &EffectStackView::reloadEffect); connect(view, &CollapsibleEffectView::switchHeight, this, &EffectStackView::slotAdjustDelegate, Qt::DirectConnection); connect(view, &CollapsibleEffectView::startDrag, this, &EffectStackView::slotStartDrag); connect(view, &CollapsibleEffectView::createGroup, m_model.get(), &EffectStackModel::slotCreateGroup); connect(view, &CollapsibleEffectView::activateEffect, this, &EffectStackView::slotActivateEffect); connect(this, &EffectStackView::blockWheenEvent, view, &CollapsibleEffectView::blockWheenEvent); connect(view, &CollapsibleEffectView::seekToPos, [this](int pos) { // at this point, the effects returns a pos relative to the clip. We need to convert it to a global time int clipIn = pCore->getItemPosition(m_model->getOwnerId()); emit seekToPos(pos + clipIn); }); connect(this, &EffectStackView::doActivateEffect, view, &CollapsibleEffectView::slotActivateEffect); QModelIndex ix = m_model->getIndexFromItem(effectModel); m_effectsTree->setIndexWidget(ix, view); auto *del = static_cast(m_effectsTree->itemDelegate(ix)); del->setHeight(ix, view->height()); view->buttonUp->setEnabled(i > 0); view->buttonDown->setEnabled(i < max - 1); if (i == active) { m_model->setActiveEffect(i); emit doActivateEffect(ix); } } updateTreeHeight(); qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! loadEffects"; } void EffectStackView::updateTreeHeight() { // For some reason, the treeview height does not update correctly, so enforce it m_mutex.lock(); int totalHeight = 0; for (int j = 0; j < m_model->rowCount(); j++) { std::shared_ptr item2 = m_model->getEffectStackRow(j); std::shared_ptr eff = std::static_pointer_cast(item2); QModelIndex idx = m_model->getIndexFromItem(eff); auto w = m_effectsTree->indexWidget(idx); if (w) { totalHeight += w->height(); } } m_effectsTree->setFixedHeight(totalHeight); m_mutex.unlock(); m_scrollTimer.start(); } void EffectStackView::slotActivateEffect(const std::shared_ptr &effectModel) { qDebug() << "MUTEX LOCK!!!!!!!!!!!! slotactivateeffect: " << effectModel->row(); QMutexLocker lock(&m_mutex); m_model->setActiveEffect(effectModel->row()); QModelIndex activeIx = m_model->getIndexFromItem(effectModel); emit doActivateEffect(activeIx); qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! slotactivateeffect"; } void EffectStackView::slotStartDrag(const QPixmap &pix, const std::shared_ptr &effectModel) { auto *drag = new QDrag(this); drag->setPixmap(pix); auto *mime = new QMimeData; mime->setData(QStringLiteral("kdenlive/effect"), effectModel->getAssetId().toUtf8()); // TODO this will break if source effect is not on the stack of a timeline clip ObjectId source = effectModel->getOwnerId(); QByteArray effectSource; effectSource += QString::number((int)source.first).toUtf8(); effectSource += '-'; effectSource += QString::number((int)source.second).toUtf8(); effectSource += '-'; effectSource += QString::number(effectModel->row()).toUtf8(); mime->setData(QStringLiteral("kdenlive/effectsource"), effectSource); // mime->setData(QStringLiteral("kdenlive/effectrow"), QString::number(effectModel->row()).toUtf8()); // Assign ownership of the QMimeData object to the QDrag object. drag->setMimeData(mime); // Start the drag and drop operation drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction); } void EffectStackView::slotAdjustDelegate(const std::shared_ptr &effectModel, int newHeight) { if (!m_model) { return; } QModelIndex ix = m_model->getIndexFromItem(effectModel); - auto *del = static_cast(m_effectsTree->itemDelegate(ix)); - if (del) { - del->setHeight(ix, newHeight); - QMetaObject::invokeMethod(this, "updateTreeHeight", Qt::QueuedConnection); + if (ix.isValid()) { + auto *del = static_cast(m_effectsTree->itemDelegate(ix)); + if (del) { + del->setHeight(ix, newHeight); + QMetaObject::invokeMethod(this, "updateTreeHeight", Qt::QueuedConnection); + } } } void EffectStackView::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); m_scrollTimer.start(); } void EffectStackView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { Q_UNUSED(roles) if (!topLeft.isValid() || !bottomRight.isValid()) { loadEffects(); return; } for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { for (int j = topLeft.column(); j <= bottomRight.column(); ++j) { CollapsibleEffectView *w = static_cast(m_effectsTree->indexWidget(m_model->index(i, j, topLeft.parent()))); if (w) { w->refresh(); } } } } void EffectStackView::unsetModel(bool reset) { // Release ownership of smart pointer Kdenlive::MonitorId id = Kdenlive::NoMonitor; if (m_model) { ObjectId item = m_model->getOwnerId(); id = item.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor; disconnect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh); disconnect(this, &EffectStackView::removeCurrentEffect, m_model.get(), &EffectStackModel::removeCurrentEffect); } if (reset) { QMutexLocker lock(&m_mutex); m_model.reset(); m_effectsTree->setModel(nullptr); } if (id != Kdenlive::NoMonitor) { pCore->getMonitor(id)->slotShowEffectScene(MonitorSceneDefault); } } ObjectId EffectStackView::stackOwner() const { if (m_model) { return m_model->getOwnerId(); } return ObjectId(ObjectType::NoItem, -1); } bool EffectStackView::addEffect(const QString &effectId) { if (m_model) { return m_model->appendEffect(effectId, true); } return false; } bool EffectStackView::isEmpty() const { return m_model == nullptr ? true : m_model->rowCount() == 0; } void EffectStackView::enableStack(bool enable) { if (m_model) { m_model->setEffectStackEnabled(enable); } } bool EffectStackView::isStackEnabled() const { if (m_model) { return m_model->isStackEnabled(); } return false; } /* void EffectStackView::switchBuiltStack(bool show) { m_builtStack->setVisible(show); m_effectsTree->setVisible(!show); KdenliveSettings::setShowbuiltstack(show); } */ diff --git a/src/timeline2/view/timelinewidget.cpp b/src/timeline2/view/timelinewidget.cpp index 86fab3cba..ffdfa7cfd 100644 --- a/src/timeline2/view/timelinewidget.cpp +++ b/src/timeline2/view/timelinewidget.cpp @@ -1,236 +1,238 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "timelinewidget.h" #include "../model/builders/meltBuilder.hpp" #include "assets/keyframes/model/keyframemodel.hpp" #include "assets/model/assetparametermodel.hpp" #include "capture/mediacapture.h" #include "core.h" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "effects/effectlist/model/effectfilter.hpp" #include "effects/effectlist/model/effecttreemodel.hpp" #include "kdenlivesettings.h" #include "mainwindow.h" #include "profiles/profilemodel.hpp" #include "project/projectmanager.h" #include "monitor/monitorproxy.h" #include "qml/timelineitems.h" #include "qmltypes/thumbnailprovider.h" #include "timelinecontroller.h" #include "transitions/transitionlist/model/transitionfilter.hpp" #include "transitions/transitionlist/model/transitiontreemodel.hpp" #include "utils/clipboardproxy.hpp" #include // #include #include #include #include #include #include +#include #include const int TimelineWidget::comboScale[] = {1, 2, 4, 8, 15, 30, 50, 75, 100, 150, 200, 300, 500, 800, 1000, 1500, 2000, 3000, 6000, 15000, 30000}; TimelineWidget::TimelineWidget(QWidget *parent) : QQuickWidget(parent) { KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); kdeclarative.setupEngine(engine()); kdeclarative.setupContext(); setClearColor(palette().window().color()); registerTimelineItems(); // Build transition model for context menu m_transitionModel = TransitionTreeModel::construct(true, this); m_transitionProxyModel = std::make_unique(this); static_cast(m_transitionProxyModel.get())->setFilterType(true, TransitionType::Favorites); m_transitionProxyModel->setSourceModel(m_transitionModel.get()); m_transitionProxyModel->setSortRole(AssetTreeModel::NameRole); m_transitionProxyModel->sort(0, Qt::AscendingOrder); // Build effects model for context menu m_effectsModel = EffectTreeModel::construct(QStringLiteral(), this); m_effectsProxyModel = std::make_unique(this); static_cast(m_effectsProxyModel.get())->setFilterType(true, EffectType::Favorites); m_effectsProxyModel->setSourceModel(m_effectsModel.get()); m_effectsProxyModel->setSortRole(AssetTreeModel::NameRole); m_effectsProxyModel->sort(0, Qt::AscendingOrder); m_proxy = new TimelineController(this); connect(m_proxy, &TimelineController::zoneMoved, this, &TimelineWidget::zoneMoved); connect(m_proxy, &TimelineController::ungrabHack, this, &TimelineWidget::slotUngrabHack); setResizeMode(QQuickWidget::SizeRootObjectToView); m_thumbnailer = new ThumbnailProvider; engine()->addImageProvider(QStringLiteral("thumbnail"), m_thumbnailer); setVisible(false); + setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); setFocusPolicy(Qt::StrongFocus); } TimelineWidget::~TimelineWidget() { delete m_proxy; } void TimelineWidget::updateEffectFavorites() { rootContext()->setContextProperty("effectModel", sortedItems(KdenliveSettings::favorite_effects(), false)); } void TimelineWidget::updateTransitionFavorites() { rootContext()->setContextProperty("transitionModel", sortedItems(KdenliveSettings::favorite_transitions(), true)); } const QStringList TimelineWidget::sortedItems(const QStringList &items, bool isTransition) { QMap sortedItems; for (const QString &effect : items) { sortedItems.insert(m_proxy->getAssetName(effect, isTransition), effect); } return sortedItems.values(); } void TimelineWidget::setModel(const std::shared_ptr &model, MonitorProxy *proxy) { m_sortModel = std::make_unique(this); m_sortModel->setSourceModel(model.get()); m_sortModel->setSortRole(TimelineItemModel::SortRole); m_sortModel->sort(0, Qt::DescendingOrder); m_proxy->setModel(model); rootContext()->setContextProperty("multitrack", m_sortModel.get()); rootContext()->setContextProperty("controller", model.get()); rootContext()->setContextProperty("timeline", m_proxy); rootContext()->setContextProperty("proxy", proxy); // Create a unique id for this timeline to prevent thumbnails // leaking from one project to another because of qml's image caching rootContext()->setContextProperty("documentId", QUuid::createUuid()); rootContext()->setContextProperty("transitionModel", sortedItems(KdenliveSettings::favorite_transitions(), true)); // m_transitionProxyModel.get()); // rootContext()->setContextProperty("effectModel", m_effectsProxyModel.get()); rootContext()->setContextProperty("effectModel", sortedItems(KdenliveSettings::favorite_effects(), false)); rootContext()->setContextProperty("audiorec", pCore->getAudioDevice()); rootContext()->setContextProperty("guidesModel", pCore->projectManager()->current()->getGuideModel().get()); rootContext()->setContextProperty("clipboard", new ClipboardProxy(this)); setSource(QUrl(QStringLiteral("qrc:/qml/timeline.qml"))); connect(rootObject(), SIGNAL(mousePosChanged(int)), pCore->window(), SLOT(slotUpdateMousePosition(int))); connect(rootObject(), SIGNAL(zoomIn(bool)), pCore->window(), SLOT(slotZoomIn(bool))); connect(rootObject(), SIGNAL(zoomOut(bool)), pCore->window(), SLOT(slotZoomOut(bool))); connect(rootObject(), SIGNAL(processingDrag(bool)), pCore->window(), SIGNAL(enableUndo(bool))); connect(m_proxy, &TimelineController::seeked, proxy, &MonitorProxy::setPosition); m_proxy->setRoot(rootObject()); setVisible(true); loading = false; m_proxy->checkDuration(); } void TimelineWidget::mousePressEvent(QMouseEvent *event) { emit focusProjectMonitor(); QQuickWidget::mousePressEvent(event); } void TimelineWidget::slotChangeZoom(int value, bool zoomOnMouse) { double pixelScale = QFontMetrics(font()).maxWidth() * 2; m_proxy->setScaleFactorOnMouse(pixelScale / comboScale[value], zoomOnMouse); } void TimelineWidget::slotFitZoom() { QVariant returnedValue; double prevScale = m_proxy->scaleFactor(); QMetaObject::invokeMethod(rootObject(), "fitZoom", Q_RETURN_ARG(QVariant, returnedValue)); double scale = returnedValue.toDouble(); QMetaObject::invokeMethod(rootObject(), "scrollPos", Q_RETURN_ARG(QVariant, returnedValue)); int scrollPos = returnedValue.toInt(); if (qFuzzyCompare(prevScale, scale)) { scale = m_prevScale; scrollPos = m_scrollPos; } else { m_prevScale = prevScale; m_scrollPos = scrollPos; scrollPos = 0; } m_proxy->setScaleFactorOnMouse(scale, false); // Update zoom slider m_proxy->updateZoom(scale); QMetaObject::invokeMethod(rootObject(), "goToStart", Q_ARG(QVariant, scrollPos)); } Mlt::Tractor *TimelineWidget::tractor() { return m_proxy->tractor(); } TimelineController *TimelineWidget::controller() { return m_proxy; } std::shared_ptr TimelineWidget::model() { return m_proxy->getModel(); } void TimelineWidget::zoneUpdated(const QPoint &zone) { m_proxy->setZone(zone); } void TimelineWidget::setTool(ProjectTool tool) { rootObject()->setProperty("activeTool", (int)tool); } QPoint TimelineWidget::getTracksCount() const { return m_proxy->getTracksCount(); } void TimelineWidget::slotUngrabHack() { // Workaround bug: https://bugreports.qt.io/browse/QTBUG-59044 // https://phabricator.kde.org/D5515 if (quickWindow() && quickWindow()->mouseGrabberItem()) { quickWindow()->mouseGrabberItem()->ungrabMouse(); } } int TimelineWidget::zoomForScale(double value) const { int scale = 100.0 / value; int ix = 13; while (comboScale[ix] > scale && ix > 0) { ix--; } return ix; } void TimelineWidget::focusTimeline() { setFocus(); if (rootObject()) { rootObject()->setFocus(true); } }