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);
}
}