diff --git a/fuzzer/fuzzing.cpp b/fuzzer/fuzzing.cpp
index 239eadee4..6da6b9c1b 100644
--- a/fuzzer/fuzzing.cpp
+++ b/fuzzer/fuzzing.cpp
@@ -1,422 +1,423 @@
/***************************************************************************
* Copyright (C) 2019 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 "fuzzing.hpp"
#include "bin/model/markerlistmodel.hpp"
#include "doc/docundostack.hpp"
#include "fakeit_standalone.hpp"
#include "logger.hpp"
#include
#include
#include
#include
#include
#define private public
#define protected public
#include "assets/keyframes/model/keyframemodel.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "bin/clipcreator.hpp"
#include "bin/projectclip.h"
#include "bin/projectfolder.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "effects/effectsrepository.hpp"
#include "effects/effectstack/model/effectitemmodel.hpp"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "mltconnection.h"
#include "project/projectmanager.h"
#include "timeline2/model/clipmodel.hpp"
#include "timeline2/model/compositionmodel.hpp"
#include "timeline2/model/groupsmodel.hpp"
#include "timeline2/model/timelinefunctions.hpp"
#include "timeline2/model/timelineitemmodel.hpp"
#include "timeline2/model/timelinemodel.hpp"
#include "timeline2/model/trackmodel.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wfloat-equal"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wpedantic"
#include
#pragma GCC diagnostic pop
using namespace fakeit;
namespace {
QString createProducer(Mlt::Profile &prof, std::string color, std::shared_ptr binModel, int length, bool limited)
{
Logger::log_create_producer("test_producer", {color, binModel, length, limited});
std::shared_ptr producer = std::make_shared(prof, "color", color.c_str());
producer->set("length", length);
producer->set("out", length - 1);
Q_ASSERT(producer->is_valid());
QString binId = QString::number(binModel->getFreeClipId());
auto binClip = ProjectClip::construct(binId, QIcon(), binModel, producer);
if (limited) {
binClip->forceLimitedDuration();
}
Fun undo = []() { return true; };
Fun redo = []() { return true; };
Q_ASSERT(binModel->addItem(binClip, binModel->getRootFolder()->clipId(), undo, redo));
return binId;
}
QString createProducerWithSound(Mlt::Profile &prof, std::shared_ptr binModel)
{
Logger::log_create_producer("test_producer_sound", {binModel});
// std::shared_ptr producer = std::make_shared(prof,
// QFileInfo("../tests/small.mkv").absoluteFilePath().toStdString().c_str());
// In case the test system does not have avformat support, we can switch to the integrated blipflash producer
std::shared_ptr producer = std::make_shared(prof, "blipflash");
producer->set_in_and_out(0, 1);
producer->set("kdenlive:duration", 2);
Q_ASSERT(producer->is_valid());
QString binId = QString::number(binModel->getFreeClipId());
auto binClip = ProjectClip::construct(binId, QIcon(), binModel, producer);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
Q_ASSERT(binModel->addItem(binClip, binModel->getRootFolder()->clipId(), undo, redo));
return binId;
}
inline int modulo(int a, int b)
{
const int result = a % b;
return result >= 0 ? result : result + b;
}
namespace {
bool isIthParamARef(const rttr::method &method, size_t i)
{
QString sig = QString::fromStdString(method.get_signature().to_string());
int deb = sig.indexOf("(");
int end = sig.lastIndexOf(")");
sig = sig.mid(deb + 1, deb - end - 1);
QStringList args = sig.split(QStringLiteral(","));
return args[(int)i].contains("&") && !args[(int)i].contains("const &");
}
} // namespace
} // namespace
void fuzz(const std::string &input)
{
Logger::init();
std::stringstream ss;
ss << input;
Mlt::Profile profile;
auto binModel = pCore->projectItemModel();
binModel->clean();
std::shared_ptr undoStack = std::make_shared(nullptr);
std::shared_ptr guideModel = std::make_shared(undoStack);
TimelineModel::next_id = 0;
Mock pmMock;
When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
ProjectManager &mocked = pmMock.get();
pCore->m_projectManager = &mocked;
std::vector> all_timelines;
std::unordered_map, std::vector> all_clips, all_tracks, all_compositions;
auto update_elems = [&]() {
all_clips.clear();
all_tracks.clear();
all_compositions.clear();
for (const auto &timeline : all_timelines) {
all_clips[timeline] = {};
all_tracks[timeline] = {};
all_compositions[timeline] = {};
auto &clips = all_clips[timeline];
clips.clear();
for (const auto &c : timeline->m_allClips) {
clips.push_back(c.first);
}
std::sort(clips.begin(), clips.end());
auto &compositions = all_compositions[timeline];
compositions.clear();
for (const auto &c : timeline->m_allCompositions) {
compositions.push_back(c.first);
}
std::sort(compositions.begin(), compositions.end());
auto &tracks = all_tracks[timeline];
tracks.clear();
for (const auto &c : timeline->m_iteratorTable) {
tracks.push_back(c.first);
}
std::sort(tracks.begin(), tracks.end());
}
};
auto get_timeline = [&]() -> std::shared_ptr {
int id = 0;
ss >> id;
if (all_timelines.size() == 0) return nullptr;
id = modulo(id, (int)all_timelines.size());
return all_timelines[size_t(id)];
};
auto get_clip = [&](std::shared_ptr timeline) {
int id = 0;
ss >> id;
if (!timeline) return -1;
if (timeline->isClip(id)) return id;
if (all_timelines.size() == 0) return -1;
if (all_clips.count(timeline) == 0) return -1;
if (all_clips[timeline].size() == 0) return -1;
id = modulo(id, (int)all_clips[timeline].size());
return all_clips[timeline][id];
};
auto get_compo = [&](std::shared_ptr timeline) {
int id = 0;
ss >> id;
if (!timeline) return -1;
if (timeline->isComposition(id)) return id;
if (all_timelines.size() == 0) return -1;
if (all_compositions.count(timeline) == 0) return -1;
if (all_compositions[timeline].size() == 0) return -1;
id = modulo(id, (int)all_compositions[timeline].size());
return all_compositions[timeline][id];
};
auto get_item = [&](std::shared_ptr timeline) {
int id = 0;
ss >> id;
if (!timeline) return -1;
if (timeline->isClip(id)) return id;
if (timeline->isComposition(id)) return id;
if (all_timelines.size() == 0) return -1;
int clip_count = 0;
if (all_clips.count(timeline) > 0) {
clip_count = all_clips[timeline].size();
}
int compo_count = 0;
if (all_compositions.count(timeline) > 0) {
compo_count = all_compositions[timeline].size();
}
if (clip_count + compo_count == 0) return -1;
id = modulo(id, clip_count + compo_count);
if (id < clip_count) {
return all_clips[timeline][id];
}
return all_compositions[timeline][id - clip_count];
};
auto get_track = [&](std::shared_ptr timeline) {
int id = 0;
ss >> id;
if (!timeline) return -1;
if (timeline->isTrack(id)) return id;
if (all_timelines.size() == 0) return -1;
if (all_tracks.count(timeline) == 0) return -1;
if (all_tracks[timeline].size() == 0) return -1;
id = modulo(id, (int)all_tracks[timeline].size());
return all_tracks[timeline][id];
};
std::string c;
while (ss >> c) {
if (Logger::back_translation_table.count(c) > 0) {
// std::cout << "found=" << c;
c = Logger::back_translation_table[c];
// std::cout << " tranlated=" << c << std::endl;
if (c == "constr_TimelineModel") {
all_timelines.emplace_back(TimelineItemModel::construct(&profile, guideModel, undoStack));
} else if (c == "constr_TrackModel") {
auto timeline = get_timeline();
int id, pos = 0;
std::string name;
bool audio = false;
ss >> id >> pos >> name >> audio;
if (name == "$$") {
name = "";
}
if (pos < -1) pos = 0;
pos = std::min((int)all_tracks[timeline].size(), pos);
if (timeline) {
TrackModel::construct(timeline, -1, pos, QString::fromStdString(name), audio);
}
} else if (c == "constr_test_producer") {
std::string color;
int length = 0;
bool limited = false;
ss >> color >> length >> limited;
createProducer(profile, color, binModel, length, limited);
} else if (c == "constr_test_producer_sound") {
createProducerWithSound(profile, binModel);
} else {
// std::cout << "executing " << c << std::endl;
rttr::type target_type = rttr::type::get();
bool found = false;
for (const std::string &t : {"TimelineModel"}) {
rttr::type current_type = rttr::type::get_by_name(t);
// std::cout << "type " << t << " has methods count=" << current_type.get_methods().size() << std::endl;
if (current_type.get_method(c).is_valid()) {
found = true;
target_type = current_type;
break;
}
}
if (found) {
bool valid = true;
rttr::method target_method = target_type.get_method(c);
std::vector arguments;
rttr::variant ptr;
if (target_type == rttr::type::get()) {
if (all_timelines.size() == 0) {
valid = false;
}
ptr = get_timeline();
}
int i = -1;
for (const auto &p : target_method.get_parameter_infos()) {
++i;
std::string arg_name = p.get_name().to_string();
// std::cout << arg_name << std::endl;
if (arg_name == "compoId") {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
int compoId = get_compo(tim);
valid = valid && (compoId >= 0);
// std::cout << "got compo" << compoId << std::endl;
arguments.push_back(compoId);
} else if (arg_name == "clipId") {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
int clipId = get_clip(tim);
valid = valid && (clipId >= 0);
arguments.push_back(clipId);
// std::cout << "got clipId" << clipId << std::endl;
} else if (arg_name == "trackId") {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
int trackId = get_track(tim);
valid = valid && (trackId >= 0);
arguments.push_back(rttr::variant(trackId));
// std::cout << "got trackId" << trackId << std::endl;
} else if (arg_name == "itemId") {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
int itemId = get_item(tim);
valid = valid && (itemId >= 0);
arguments.push_back(itemId);
// std::cout << "got itemId" << itemId << std::endl;
} else if (arg_name == "ids") {
int count = 0;
ss >> count;
// std::cout << "got ids. going to read count=" << count << std::endl;
if (count > 0) {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
std::unordered_set ids;
for (int i = 0; i < count; ++i) {
int itemId = get_item(tim);
// std::cout << "\t read" << itemId << std::endl;
valid = valid && (itemId >= 0);
ids.insert(itemId);
}
arguments.push_back(ids);
} else {
valid = false;
}
} else if (!isIthParamARef(target_method, i)) {
rttr::type arg_type = p.get_type();
if (arg_type == rttr::type::get()) {
int a = 0;
ss >> a;
// std::cout << "read int " << a << std::endl;
arguments.push_back(a);
} else if (arg_type == rttr::type::get()) {
bool a = false;
ss >> a;
// std::cout << "read bool " << a << std::endl;
arguments.push_back(a);
} else if (arg_type == rttr::type::get()) {
std::string str = "";
ss >> str;
// std::cout << "read str " << str << std::endl;
if (str == "$$") {
str = "";
}
arguments.push_back(QString::fromStdString(str));
} else if (arg_type.is_enumeration()) {
int a = 0;
ss >> a;
rttr::variant var_a = a;
var_a.convert((const rttr::type &)arg_type);
// std::cout << "read enum " << arg_type.get_enumeration().value_to_name(var_a).to_string() << std::endl;
arguments.push_back(var_a);
} else {
assert(false);
}
} else {
if (p.get_type() == rttr::type::get()) {
arguments.push_back(-1);
} else {
assert(false);
}
}
}
if (valid) {
// std::cout << "VALID!!!" << std::endl;
std::vector args;
+ args.reserve(arguments.size());
for (const auto &a : arguments) {
args.emplace_back(a);
// std::cout<<"argument="<checkConsistency());
}
}
}
all_clips.clear();
all_tracks.clear();
all_compositions.clear();
for (size_t i = 0; i < all_timelines.size(); ++i) {
all_timelines[i].reset();
}
pCore->m_projectManager = nullptr;
Core::m_self.reset();
MltConnection::m_self.reset();
std::cout << "---------------------------------------------------------------------------------------------------------------------------------------------"
"---------------"
<< std::endl;
}
diff --git a/renderer/kdenlive_render.cpp b/renderer/kdenlive_render.cpp
index 41836948d..d9b568a28 100644
--- a/renderer/kdenlive_render.cpp
+++ b/renderer/kdenlive_render.cpp
@@ -1,143 +1,143 @@
/***************************************************************************
* Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) any later version. *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "framework/mlt_version.h"
#include "mlt++/Mlt.h"
#include "renderjob.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QStringList args = app.arguments();
QStringList preargs;
QString locale;
if (args.count() >= 4) {
// Remove program name
args.removeFirst();
// renderer path (melt)
QString render = args.at(0);
args.removeFirst();
// Source playlist path
QString playlist = args.at(0);
args.removeFirst();
// target - where to save result
QString target = args.at(0);
args.removeFirst();
int pid = 0;
// pid to send back progress
if (args.count() > 0 && args.at(0).startsWith(QLatin1String("-pid:"))) {
pid = args.at(0).section(QLatin1Char(':'), 1).toInt();
args.removeFirst();
}
// Do we want a split render
if (args.count() > 0 && args.at(0) == QLatin1String("-split")) {
args.removeFirst();
// chunks to render
QStringList chunks = args.at(0).split(QLatin1Char(','), QString::SkipEmptyParts);
args.removeFirst();
// chunk size in frames
int chunkSize = args.at(0).toInt();
args.removeFirst();
// rendered file extension
QString extension = args.at(0);
args.removeFirst();
// avformat consumer params
QStringList consumerParams = args.at(0).split(QLatin1Char(' '), QString::SkipEmptyParts);
args.removeFirst();
QDir baseFolder(target);
Mlt::Factory::init();
Mlt::Profile profile;
Mlt::Producer prod(profile, nullptr, playlist.toUtf8().constData());
if (!prod.is_valid()) {
fprintf(stderr, "INVALID playlist: %s \n", playlist.toUtf8().constData());
}
- for (const QString frame : chunks) {
+ for (const QString &frame : chunks) {
fprintf(stderr, "START:%d \n", frame.toInt());
QString fileName = QStringLiteral("%1.%2").arg(frame).arg(extension);
if (baseFolder.exists(fileName)) {
// Don't overwrite an existing file
fprintf(stderr, "DONE:%d \n", frame.toInt());
continue;
}
QScopedPointer playlst(prod.cut(frame.toInt(), frame.toInt() + chunkSize));
QScopedPointer cons(
new Mlt::Consumer(profile, QString("avformat:%1").arg(baseFolder.absoluteFilePath(fileName)).toUtf8().constData()));
for (const QString ¶m : consumerParams) {
if (param.contains(QLatin1Char('='))) {
cons->set(param.section(QLatin1Char('='), 0, 0).toUtf8().constData(), param.section(QLatin1Char('='), 1).toUtf8().constData());
}
}
cons->set("terminate_on_pause", 1);
cons->connect(*playlst);
playlst.reset();
cons->run();
cons->stop();
cons->purge();
fprintf(stderr, "DONE:%d \n", frame.toInt());
}
// Mlt::Factory::close();
fprintf(stderr, "+ + + RENDERING FINSHED + + + \n");
return 0;
}
int in = -1;
int out = -1;
if (LIBMLT_VERSION_INT < 396544) {
// older MLT version, does not support consumer in/out, so read it manually
QFile f(playlist);
QDomDocument doc;
doc.setContent(&f, false);
f.close();
QDomElement consumer = doc.documentElement().firstChildElement(QStringLiteral("consumer"));
if (!consumer.isNull()) {
in = consumer.attribute("in").toInt();
out = consumer.attribute("out").toInt();
}
}
RenderJob *rJob = new RenderJob(render, playlist, target, pid, in, out);
rJob->start();
return app.exec();
} else {
fprintf(stderr,
"Kdenlive video renderer for MLT.\nUsage: "
"kdenlive_render [-erase] [-kuiserver] [-locale:LOCALE] [in=pos] [out=pos] [render] [profile] [rendermodule] [player] [src] [dest] [[arg1] "
"[arg2] ...]\n"
" -erase: if that parameter is present, src file will be erased at the end\n"
" -kuiserver: if that parameter is present, use KDE job tracker\n"
" -locale:LOCALE : set a locale for rendering. For example, -locale:fr_FR.UTF-8 will use a french locale (comma as numeric separator)\n"
" in=pos: start rendering at frame pos\n"
" out=pos: end rendering at frame pos\n"
" render: path to MLT melt renderer\n"
" profile: the MLT video profile\n"
" rendermodule: the MLT consumer used for rendering, usually it is avformat\n"
" player: path to video player to play when rendering is over, use '-' to disable playing\n"
" src: source file (usually MLT XML)\n"
" dest: destination file\n"
" args: space separated libavformat arguments\n");
return 1;
}
}
diff --git a/src/abstractmodel/abstracttreemodel.cpp b/src/abstractmodel/abstracttreemodel.cpp
index f34d21cb5..299c00b95 100644
--- a/src/abstractmodel/abstracttreemodel.cpp
+++ b/src/abstractmodel/abstracttreemodel.cpp
@@ -1,349 +1,349 @@
/***************************************************************************
* 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();
}
QModelIndex AbstractTreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid()) return QModelIndex();
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 QModelIndex();
}
auto parentIndex = getIndexFromItem(item->parentItem().lock());
return index(item->row(), 0, parentIndex);
}
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 QModelIndex();
}
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(std::move(item));
+ 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(std::shared_ptr new_item, int parentId)
+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/abstractmodel/abstracttreemodel.hpp b/src/abstractmodel/abstracttreemodel.hpp
index b5671d858..24fec46ed 100644
--- a/src/abstractmodel/abstracttreemodel.hpp
+++ b/src/abstractmodel/abstracttreemodel.hpp
@@ -1,126 +1,126 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#ifndef ABSTRACTTREEMODEL_H
#define ABSTRACTTREEMODEL_H
#include "undohelper.hpp"
#include
#include
#include
/* @brief This class represents a generic tree hierarchy
*/
class TreeItem;
class AbstractTreeModel : public QAbstractItemModel, public std::enable_shared_from_this
{
Q_OBJECT
public:
/* @brief Construct a TreeModel
@param parent is the parent object of the model
@return a ptr to the created object
*/
static std::shared_ptr construct(QObject *parent = nullptr);
protected:
// This is protected. Call construct instead.
explicit AbstractTreeModel(QObject *parent = nullptr);
public:
virtual ~AbstractTreeModel();
/* @brief Given an item from the hierarchy, construct the corresponding ModelIndex */
QModelIndex getIndexFromItem(const std::shared_ptr &item) const;
/* @brief Given an item id, construct the corresponding ModelIndex */
QModelIndex getIndexFromId(int id) const;
/* @brief Return a ptr to an item given its id */
std::shared_ptr getItemById(int id) const;
/* @brief Return a ptr to the root of the tree */
std::shared_ptr getRoot() const;
QVariant data(const QModelIndex &index, int role) const override;
// This is reimplemented to prevent selection of the categories
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
/* @brief Helper function to generate a lambda that adds an item to the tree */
- Fun addItem_lambda(std::shared_ptr new_item, int parentId);
+ Fun addItem_lambda(const std::shared_ptr &new_item, int parentId);
/* @brief Helper function to generate a lambda that removes an item from the tree */
Fun removeItem_lambda(int id);
/* @brief Helper function to generate a lambda that changes the row of an item */
Fun moveItem_lambda(int id, int destRow, bool force = false);
friend class TreeItem;
friend class AbstractProjectItem;
protected:
/* @brief Register a new item. This is a call-back meant to be called from TreeItem */
virtual void registerItem(const std::shared_ptr &item);
/* @brief Deregister an item. This is a call-back meant to be called from TreeItem */
virtual void deregisterItem(int id, TreeItem *item);
/* @brief Returns the next valid id to give to a new element */
static int getNextId();
/* @brief Send the appropriate notification related to a row that we are appending
@param item is the parent item to which row is appended
*/
void notifyRowAboutToAppend(const std::shared_ptr &item);
/* @brief Send the appropriate notification related to a row that we have appended
@param row is the new element
*/
void notifyRowAppended(const std::shared_ptr &row);
/* @brief Send the appropriate notification related to a row that we are deleting
@param item is the parent of the row being deleted
@param row is the index of the row being deleted
*/
void notifyRowAboutToDelete(std::shared_ptr item, int row);
/* @brief Send the appropriate notification related to a row that we have appended
@param row is the old element
*/
void notifyRowDeleted();
/* @brief This is a convenience function that helps check if the tree is in a valid state */
virtual bool checkConsistency();
protected:
std::shared_ptr rootItem;
std::unordered_map> m_allItems;
static int currentTreeId;
};
#endif
diff --git a/src/abstractmodel/treeitem.cpp b/src/abstractmodel/treeitem.cpp
index 4fde1caf2..b0d97f9f2 100644
--- a/src/abstractmodel/treeitem.cpp
+++ b/src/abstractmodel/treeitem.cpp
@@ -1,291 +1,291 @@
/***************************************************************************
* 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 "treeitem.hpp"
#include "abstracttreemodel.hpp"
#include
#include
#include
TreeItem::TreeItem(const QList &data, const std::shared_ptr &model, bool isRoot, int id)
: m_itemData(data)
, m_model(model)
, m_depth(0)
, m_id(id == -1 ? AbstractTreeModel::getNextId() : id)
, m_isInModel(false)
, m_isRoot(isRoot)
{
}
std::shared_ptr TreeItem::construct(const QList &data, std::shared_ptr model, bool isRoot, int id)
{
- std::shared_ptr self(new TreeItem(data, std::move(model), isRoot, id));
+ std::shared_ptr self(new TreeItem(data, model, isRoot, id));
baseFinishConstruct(self);
return self;
}
// static
void TreeItem::baseFinishConstruct(const std::shared_ptr &self)
{
if (self->m_isRoot) {
registerSelf(self);
}
}
TreeItem::~TreeItem()
{
deregisterSelf();
}
std::shared_ptr TreeItem::appendChild(const QList &data)
{
if (auto ptr = m_model.lock()) {
auto child = construct(data, ptr, false);
appendChild(child);
return child;
}
qDebug() << "ERROR: Something went wrong when appending child in TreeItem. Model is not available anymore";
Q_ASSERT(false);
return std::shared_ptr();
}
-bool TreeItem::appendChild(std::shared_ptr child)
+bool TreeItem::appendChild(const std::shared_ptr &child)
{
if (hasAncestor(child->getId())) {
// in that case, we are trying to create a cycle, abort
return false;
}
if (auto oldParent = child->parentItem().lock()) {
if (oldParent->getId() == m_id) {
// no change needed
return true;
} else {
// in that case a call to removeChild should have been carried out
qDebug() << "ERROR: trying to append a child that alrealdy has a parent";
return false;
}
}
if (auto ptr = m_model.lock()) {
ptr->notifyRowAboutToAppend(shared_from_this());
child->updateParent(shared_from_this());
int id = child->getId();
auto it = m_childItems.insert(m_childItems.end(), child);
m_iteratorTable[id] = it;
registerSelf(child);
ptr->notifyRowAppended(child);
return true;
}
qDebug() << "ERROR: Something went wrong when appending child in TreeItem. Model is not available anymore";
Q_ASSERT(false);
return false;
}
-void TreeItem::moveChild(int ix, std::shared_ptr child)
+void TreeItem::moveChild(int ix, const std::shared_ptr &child)
{
if (auto ptr = m_model.lock()) {
auto parentPtr = child->m_parentItem.lock();
if (parentPtr && parentPtr->getId() != m_id) {
parentPtr->removeChild(child);
} else {
// deletion of child
auto it = m_iteratorTable[child->getId()];
m_childItems.erase(it);
}
ptr->notifyRowAboutToAppend(shared_from_this());
child->updateParent(shared_from_this());
int id = child->getId();
auto pos = m_childItems.begin();
std::advance(pos, ix);
auto it = m_childItems.insert(pos, child);
m_iteratorTable[id] = it;
ptr->notifyRowAppended(child);
m_isInModel = true;
} else {
qDebug() << "ERROR: Something went wrong when moving child in TreeItem. Model is not available anymore";
Q_ASSERT(false);
}
}
void TreeItem::removeChild(const std::shared_ptr &child)
{
if (auto ptr = m_model.lock()) {
ptr->notifyRowAboutToDelete(shared_from_this(), child->row());
// get iterator corresponding to child
Q_ASSERT(m_iteratorTable.count(child->getId()) > 0);
auto it = m_iteratorTable[child->getId()];
// deletion of child
m_childItems.erase(it);
// clean iterator table
m_iteratorTable.erase(child->getId());
child->m_depth = 0;
child->m_parentItem.reset();
child->deregisterSelf();
ptr->notifyRowDeleted();
} else {
qDebug() << "ERROR: Something went wrong when removing child in TreeItem. Model is not available anymore";
Q_ASSERT(false);
}
}
bool TreeItem::changeParent(std::shared_ptr newParent)
{
Q_ASSERT(!m_isRoot);
if (m_isRoot) return false;
std::shared_ptr oldParent;
if ((oldParent = m_parentItem.lock())) {
oldParent->removeChild(shared_from_this());
}
bool res = true;
if (newParent) {
res = newParent->appendChild(shared_from_this());
if (res) {
m_parentItem = newParent;
} else if (oldParent) {
// something went wrong, we have to reset the parent.
bool reverse = oldParent->appendChild(shared_from_this());
Q_ASSERT(reverse);
}
}
return res;
}
std::shared_ptr TreeItem::child(int row) const
{
Q_ASSERT(row >= 0 && row < (int)m_childItems.size());
auto it = m_childItems.cbegin();
std::advance(it, row);
return (*it);
}
int TreeItem::childCount() const
{
return (int)m_childItems.size();
}
int TreeItem::columnCount() const
{
return m_itemData.count();
}
QVariant TreeItem::dataColumn(int column) const
{
return m_itemData.value(column);
}
-void TreeItem::setData(int column, const QVariant dataColumn)
+void TreeItem::setData(int column, const QVariant &dataColumn)
{
m_itemData[column] = dataColumn;
}
std::weak_ptr TreeItem::parentItem() const
{
return m_parentItem;
}
int TreeItem::row() const
{
if (auto ptr = m_parentItem.lock()) {
// we compute the distance in the parent's children list
auto it = ptr->m_childItems.begin();
return (int)std::distance(it, (decltype(it))ptr->m_iteratorTable.at(m_id));
}
return -1;
}
int TreeItem::depth() const
{
return m_depth;
}
int TreeItem::getId() const
{
return m_id;
}
bool TreeItem::isInModel() const
{
return m_isInModel;
}
-void TreeItem::registerSelf(std::shared_ptr self)
+void TreeItem::registerSelf(const std::shared_ptr &self)
{
for (const auto &child : self->m_childItems) {
registerSelf(child);
}
if (auto ptr = self->m_model.lock()) {
ptr->registerItem(self);
self->m_isInModel = true;
} else {
qDebug() << "Error : construction of treeItem failed because parent model is not available anymore";
Q_ASSERT(false);
}
}
void TreeItem::deregisterSelf()
{
for (const auto &child : m_childItems) {
child->deregisterSelf();
}
if (m_isInModel) {
if (auto ptr = m_model.lock()) {
ptr->deregisterItem(m_id, this);
m_isInModel = false;
}
}
}
bool TreeItem::hasAncestor(int id)
{
if (m_id == id) {
return true;
}
if (auto ptr = m_parentItem.lock()) {
return ptr->hasAncestor(id);
}
return false;
}
bool TreeItem::isRoot() const
{
return m_isRoot;
}
void TreeItem::updateParent(std::shared_ptr parent)
{
m_parentItem = parent;
if (parent) {
m_depth = parent->m_depth + 1;
}
}
std::vector> TreeItem::getLeaves()
{
if (childCount() == 0) {
return {shared_from_this()};
}
std::vector> leaves;
for (const auto &c : m_childItems) {
for (const auto &l : c->getLeaves()) {
leaves.push_back(l);
}
}
return leaves;
}
diff --git a/src/abstractmodel/treeitem.hpp b/src/abstractmodel/treeitem.hpp
index b856fcb47..889c3fe13 100644
--- a/src/abstractmodel/treeitem.hpp
+++ b/src/abstractmodel/treeitem.hpp
@@ -1,189 +1,189 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#ifndef TREEITEM_H
#define TREEITEM_H
#include "definitions.h"
#include
#include
#include
#include
/* @brief This class is a generic class to represent items of a tree-like model
It works in tandem with AbstractTreeModel or one of its derived classes.
There is a registration mechanism that takes place: each TreeItem holds a unique Id
that can allow to retrieve it directly from the model.
A TreeItem registers itself to the model as soon as it gets a proper parent (the node
above it in the hierarchy). This means that upon creation, the TreeItem is NOT
registered, because at this point it doesn't belong to any parent.
The only exception is for the rootItem, which is always registered.
Note that the root is a special object. In particular, it must stay at the root and
must not be declared as the child of any other item.
*/
class AbstractTreeModel;
class TreeItem : public enable_shared_from_this_virtual
{
public:
/* @brief Construct a TreeItem
@param data List of data elements (columns) of the created item
@param model Pointer to the model to which this elem belongs to
@param parentItem address of the parent if the child is not orphan
@param isRoot is true if the object is the topmost item of the tree
@param id of the newly created item. If left to -1, the id is assigned automatically
@return a ptr to the constructed item
*/
static std::shared_ptr construct(const QList &data, std::shared_ptr model, bool isRoot, int id = -1);
friend class AbstractTreeModel;
protected:
// This is protected. Call construct instead
explicit TreeItem(const QList &data, const std::shared_ptr &model, bool isRoot, int id = -1);
public:
virtual ~TreeItem();
/* @brief Creates a child of the current item
@param data: List of data elements (columns) to init the child with.
*/
std::shared_ptr appendChild(const QList &data);
/* @brief Appends an already created child
Useful for example if the child should be a subclass of TreeItem
@return true on success. Otherwise, nothing is modified.
*/
- bool appendChild(std::shared_ptr child);
- void moveChild(int ix, std::shared_ptr child);
+ bool appendChild(const std::shared_ptr &child);
+ void moveChild(int ix, const std::shared_ptr &child);
/* @brief Remove given child from children list. The parent of the child is updated
accordingly
*/
void removeChild(const std::shared_ptr &child);
/* @brief Change the parent of the current item. Structures are modified accordingly
*/
virtual bool changeParent(std::shared_ptr newParent);
/* @brief Retrieves a child of the current item
@param row is the index of the child to retrieve
*/
std::shared_ptr child(int row) const;
/* @brief Returns a vector containing a pointer to all the leaves in the subtree rooted in this element */
std::vector> getLeaves();
/* @brief Return the number of children */
int childCount() const;
/* @brief Return the number of data fields (columns) */
int columnCount() const;
/* @brief Return the content of a column
@param column Index of the column to look-up
*/
QVariant dataColumn(int column) const;
- void setData(int column, const QVariant dataColumn);
+ void setData(int column, const QVariant &dataColumn);
/* @brief Return the index of current item amongst father's children
Returns -1 on error (eg: no parent set)
*/
int row() const;
/* @brief Return a ptr to the parent item
*/
std::weak_ptr parentItem() const;
/* @brief Return the depth of the current item*/
int depth() const;
/* @brief Return the id of the current item*/
int getId() const;
/* @brief Return true if the current item has been registered */
bool isInModel() const;
/* @brief This is similar to the std::accumulate function, except that it
operates on the whole subtree
@param init is the initial value of the operation
@param is the binary op to apply (signature should be (T, shared_ptr)->T)
*/
template T accumulate(T init, BinaryOperation op);
template T accumulate_const(T init, BinaryOperation op) const;
/* @brief Return true if the current item has the item with given id as an ancestor */
bool hasAncestor(int id);
/* @brief Return true if the item thinks it is a root.
Note that it should be consistent with what the model thinks, but it may have been
messed up at some point if someone wrongly constructed the object with isRoot = true */
bool isRoot() const;
protected:
/* @brief Finish construction of object given its pointer
This is a separated function so that it can be called from derived classes */
static void baseFinishConstruct(const std::shared_ptr &self);
/* @brief Helper functions to handle registration / deregistration to the model */
- static void registerSelf(std::shared_ptr self);
+ static void registerSelf(const std::shared_ptr &self);
void deregisterSelf();
/* @brief Reflect update of the parent ptr (for example set the correct depth)
This is meant to be overridden in derived classes
@param ptr is the pointer to the new parent
*/
virtual void updateParent(std::shared_ptr parent);
std::list> m_childItems;
std::unordered_map>::iterator>
m_iteratorTable; // this logs the iterator associated which each child id. This allows easy access of a child based on its id.
QList m_itemData;
std::weak_ptr m_parentItem;
std::weak_ptr m_model;
int m_depth;
int m_id;
bool m_isInModel;
bool m_isRoot;
};
template T TreeItem::accumulate(T init, BinaryOperation op)
{
T res = op(init, shared_from_this());
for (const auto &c : m_childItems) {
res = c->accumulate(res, op);
}
return res;
}
template T TreeItem::accumulate_const(T init, BinaryOperation op) const
{
T res = op(init, shared_from_this());
for (const auto &c : m_childItems) {
res = c->accumulate_const(res, op);
}
return res;
}
#endif
diff --git a/src/assets/assetlist/model/assetfilter.cpp b/src/assets/assetlist/model/assetfilter.cpp
index 1c9c23c54..86a4341fa 100644
--- a/src/assets/assetlist/model/assetfilter.cpp
+++ b/src/assets/assetlist/model/assetfilter.cpp
@@ -1,176 +1,176 @@
/***************************************************************************
* 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 "assetfilter.hpp"
#include "abstractmodel/abstracttreemodel.hpp"
#include "abstractmodel/treeitem.hpp"
#include "assettreemodel.hpp"
#include
AssetFilter::AssetFilter(QObject *parent)
: QSortFilterProxyModel(parent)
, m_name_enabled(false)
{
setFilterRole(Qt::DisplayRole);
setSortRole(Qt::DisplayRole);
setDynamicSortFilter(false);
}
void AssetFilter::setFilterName(bool enabled, const QString &pattern)
{
m_name_enabled = enabled;
m_name_value = pattern;
invalidateFilter();
if (rowCount() > 1) {
sort(0);
}
}
bool AssetFilter::filterName(const std::shared_ptr &item) const
{
if (!m_name_enabled) {
return true;
}
QString itemText = item->dataColumn(AssetTreeModel::nameCol).toString();
itemText = itemText.normalized(QString::NormalizationForm_D).remove(QRegExp(QStringLiteral("[^a-zA-Z0-9\\s]")));
QString patt = m_name_value.normalized(QString::NormalizationForm_D).remove(QRegExp(QStringLiteral("[^a-zA-Z0-9\\s]")));
return itemText.contains(patt, Qt::CaseInsensitive);
}
bool AssetFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex row = sourceModel()->index(sourceRow, 0, sourceParent);
auto *model = static_cast(sourceModel());
std::shared_ptr item = model->getItemById((int)row.internalId());
if (item->dataColumn(AssetTreeModel::idCol) == QStringLiteral("root")) {
// In that case, we have a category. We hide it if it does not have children.
QModelIndex category = sourceModel()->index(sourceRow, 0, sourceParent);
if (!category.isValid()) {
return false;
}
bool accepted = false;
for (int i = 0; i < sourceModel()->rowCount(category) && !accepted; ++i) {
accepted = filterAcceptsRow(i, category);
}
return accepted;
}
return applyAll(item);
}
bool AssetFilter::isVisible(const QModelIndex &sourceIndex)
{
auto parent = sourceModel()->parent(sourceIndex);
return filterAcceptsRow(sourceIndex.row(), parent);
}
bool AssetFilter::applyAll(std::shared_ptr item) const
{
- return filterName(std::move(item));
+ return filterName(item);
}
QModelIndex AssetFilter::getNextChild(const QModelIndex ¤t)
{
QModelIndex nextItem = current.sibling(current.row() + 1, current.column());
if (!nextItem.isValid()) {
QModelIndex folder = index(current.parent().row() + 1, 0, QModelIndex());
if (!folder.isValid()) {
return current;
}
while (folder.isValid() && rowCount(folder) == 0) {
folder = folder.sibling(folder.row() + 1, folder.column());
}
if (folder.isValid() && rowCount(folder) > 0) {
return index(0, current.column(), folder);
}
nextItem = current;
}
return nextItem;
}
QModelIndex AssetFilter::getPreviousChild(const QModelIndex ¤t)
{
QModelIndex nextItem = current.sibling(current.row() - 1, current.column());
if (!nextItem.isValid()) {
QModelIndex folder = index(current.parent().row() - 1, 0, QModelIndex());
if (!folder.isValid()) {
return current;
}
while (folder.isValid() && rowCount(folder) == 0) {
folder = folder.sibling(folder.row() - 1, folder.column());
}
if (folder.isValid() && rowCount(folder) > 0) {
return index(rowCount(folder) - 1, current.column(), folder);
}
nextItem = current;
}
return nextItem;
}
QModelIndex AssetFilter::firstVisibleItem(const QModelIndex ¤t)
{
if (current.isValid() && isVisible(mapToSource(current))) {
return current;
}
QModelIndex folder = index(0, 0, QModelIndex());
if (!folder.isValid()) {
return current;
}
while (folder.isValid() && rowCount(folder) == 0) {
folder = index(folder.row() + 1, 0, QModelIndex());
}
if (rowCount(folder) > 0) {
return index(0, 0, folder);
}
return current;
}
QModelIndex AssetFilter::getCategory(int catRow)
{
QModelIndex cat = index(catRow, 0, QModelIndex());
return cat;
}
QVariantList AssetFilter::getCategories()
{
QVariantList list;
for (int i = 0; i < sourceModel()->rowCount(); i++) {
QModelIndex cat = getCategory(i);
if (cat.isValid()) {
list << cat;
}
}
return list;
}
QModelIndex AssetFilter::getModelIndex(QModelIndex current)
{
QModelIndex sourceIndex = mapToSource(current);
return sourceIndex; // this returns an integer
}
QModelIndex AssetFilter::getProxyIndex(QModelIndex current)
{
QModelIndex sourceIndex = mapFromSource(current);
return sourceIndex; // this returns an integer
}
diff --git a/src/assets/assetpanel.cpp b/src/assets/assetpanel.cpp
index 7fa04e104..6ea3f0269 100644
--- a/src/assets/assetpanel.cpp
+++ b/src/assets/assetpanel.cpp
@@ -1,360 +1,360 @@
/***************************************************************************
* 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 "assetpanel.hpp"
#include "core.h"
#include "definitions.h"
#include "effects/effectstack/model/effectitemmodel.hpp"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "effects/effectstack/view/effectstackview.hpp"
#include "kdenlivesettings.h"
#include "model/assetparametermodel.hpp"
#include "transitions/transitionsrepository.hpp"
#include "transitions/view/transitionstackview.hpp"
#include "view/assetparameterview.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
AssetPanel::AssetPanel(QWidget *parent)
: QWidget(parent)
, m_lay(new QVBoxLayout(this))
, m_assetTitle(new KSqueezedTextLabel(this))
, m_container(new QWidget(this))
, m_transitionWidget(new TransitionStackView(this))
, m_effectStackWidget(new EffectStackView(this))
{
QToolBar *buttonToolbar = new QToolBar(this);
buttonToolbar->addWidget(m_assetTitle);
int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
QSize iconSize(size, size);
buttonToolbar->setIconSize(iconSize);
// spacer
QWidget *empty = new QWidget();
empty->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
buttonToolbar->addWidget(empty);
m_switchBuiltStack = new QToolButton(this);
m_switchBuiltStack->setIcon(QIcon::fromTheme(QStringLiteral("adjustlevels")));
m_switchBuiltStack->setToolTip(i18n("Adjust clip"));
m_switchBuiltStack->setCheckable(true);
m_switchBuiltStack->setChecked(KdenliveSettings::showbuiltstack());
m_switchBuiltStack->setVisible(false);
// connect(m_switchBuiltStack, &QToolButton::toggled, m_effectStackWidget, &EffectStackView::switchBuiltStack);
buttonToolbar->addWidget(m_switchBuiltStack);
m_splitButton = new KDualAction(i18n("Normal view"), i18n("Compare effect"), this);
m_splitButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("view-right-close")));
m_splitButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
m_splitButton->setToolTip(i18n("Compare effect"));
m_splitButton->setVisible(false);
connect(m_splitButton, &KDualAction::activeChangedByUser, this, &AssetPanel::processSplitEffect);
buttonToolbar->addAction(m_splitButton);
m_enableStackButton = new KDualAction(i18n("Effects disabled"), i18n("Effects enabled"), this);
m_enableStackButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("hint")));
m_enableStackButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("visibility")));
connect(m_enableStackButton, &KDualAction::activeChangedByUser, this, &AssetPanel::enableStack);
m_enableStackButton->setVisible(false);
buttonToolbar->addAction(m_enableStackButton);
m_timelineButton = new KDualAction(i18n("Hide keyframes"), i18n("Display keyframes in timeline"), this);
m_timelineButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("adjustlevels")));
m_timelineButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("adjustlevels")));
m_timelineButton->setToolTip(i18n("Display keyframes in timeline"));
m_timelineButton->setVisible(false);
connect(m_timelineButton, &KDualAction::activeChangedByUser, this, &AssetPanel::showKeyframes);
buttonToolbar->addAction(m_timelineButton);
m_lay->addWidget(buttonToolbar);
m_lay->setContentsMargins(0, 0, 0, 0);
m_lay->setSpacing(0);
QVBoxLayout *lay = new QVBoxLayout(m_container);
lay->setContentsMargins(0, 0, 0, 0);
lay->addWidget(m_transitionWidget);
lay->addWidget(m_effectStackWidget);
QScrollArea *sc = new QScrollArea;
sc->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
sc->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
sc->setFrameStyle(QFrame::NoFrame);
sc->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding));
m_container->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding));
sc->setWidgetResizable(true);
m_lay->addWidget(sc);
sc->setWidget(m_container);
m_transitionWidget->setVisible(false);
m_effectStackWidget->setVisible(false);
updatePalette();
connect(m_effectStackWidget, &EffectStackView::seekToPos, this, &AssetPanel::seekToPos);
connect(m_effectStackWidget, &EffectStackView::reloadEffect, this, &AssetPanel::reloadEffect);
connect(m_transitionWidget, &TransitionStackView::seekToTransPos, this, &AssetPanel::seekToPos);
connect(m_effectStackWidget, &EffectStackView::updateEnabledState, [this]() { m_enableStackButton->setActive(m_effectStackWidget->isStackEnabled()); });
}
-void AssetPanel::showTransition(int tid, std::shared_ptr transitionModel)
+void AssetPanel::showTransition(int tid, const std::shared_ptr &transitionModel)
{
Q_UNUSED(tid)
ObjectId id = transitionModel->getOwnerId();
if (m_transitionWidget->stackOwner() == id) {
// already on this effect stack, do nothing
return;
}
clear();
QString transitionId = transitionModel->getAssetId();
QString transitionName = TransitionsRepository::get()->getName(transitionId);
m_assetTitle->setText(i18n("%1 properties", transitionName));
m_transitionWidget->setVisible(true);
m_timelineButton->setVisible(true);
m_enableStackButton->setVisible(false);
m_transitionWidget->setModel(transitionModel, QSize(), true);
}
-void AssetPanel::showEffectStack(const QString &itemName, std::shared_ptr effectsModel, QSize frameSize, bool showKeyframes)
+void AssetPanel::showEffectStack(const QString &itemName, const std::shared_ptr &effectsModel, QSize frameSize, bool showKeyframes)
{
m_splitButton->setActive(false);
if (effectsModel == nullptr) {
// Item is not ready
m_splitButton->setVisible(false);
m_enableStackButton->setVisible(false);
clear();
return;
}
ObjectId id = effectsModel->getOwnerId();
if (m_effectStackWidget->stackOwner() == id) {
// already on this effect stack, do nothing
return;
}
clear();
QString title;
bool showSplit = false;
bool enableKeyframes = false;
switch (id.first) {
case ObjectType::TimelineClip:
title = i18n("%1 effects", itemName);
showSplit = true;
enableKeyframes = true;
break;
case ObjectType::TimelineComposition:
title = i18n("%1 parameters", itemName);
enableKeyframes = true;
break;
case ObjectType::TimelineTrack:
title = i18n("Track %1 effects", itemName);
// TODO: track keyframes
// enableKeyframes = true;
break;
case ObjectType::BinClip:
title = i18n("Bin %1 effects", itemName);
showSplit = true;
break;
default:
title = itemName;
break;
}
m_assetTitle->setText(title);
m_splitButton->setVisible(showSplit);
m_enableStackButton->setVisible(id.first != ObjectType::TimelineComposition);
m_enableStackButton->setActive(effectsModel->isStackEnabled());
if (showSplit) {
m_splitButton->setEnabled(effectsModel->rowCount() > 0);
QObject::connect(effectsModel.get(), &EffectStackModel::dataChanged, [&]() {
if (m_effectStackWidget->isEmpty()) {
m_splitButton->setActive(false);
}
m_splitButton->setEnabled(!m_effectStackWidget->isEmpty());
});
}
m_timelineButton->setVisible(enableKeyframes);
m_timelineButton->setActive(showKeyframes);
// Disable built stack until properly implemented
// m_switchBuiltStack->setVisible(true);
m_effectStackWidget->setVisible(true);
m_effectStackWidget->setModel(effectsModel, frameSize);
}
void AssetPanel::clearAssetPanel(int itemId)
{
ObjectId id = m_effectStackWidget->stackOwner();
if (id.first == ObjectType::TimelineClip && id.second == itemId) {
clear();
} else {
id = m_transitionWidget->stackOwner();
if (id.first == ObjectType::TimelineComposition && id.second == itemId) {
clear();
}
}
}
void AssetPanel::clear()
{
m_transitionWidget->setVisible(false);
m_transitionWidget->unsetModel();
m_effectStackWidget->setVisible(false);
m_splitButton->setVisible(false);
m_timelineButton->setVisible(false);
m_switchBuiltStack->setVisible(false);
m_effectStackWidget->unsetModel();
m_assetTitle->setText(QString());
}
void AssetPanel::updatePalette()
{
QString styleSheet = getStyleSheet();
setStyleSheet(styleSheet);
m_transitionWidget->setStyleSheet(styleSheet);
m_effectStackWidget->setStyleSheet(styleSheet);
}
// static
const QString AssetPanel::getStyleSheet()
{
KColorScheme scheme(QApplication::palette().currentColorGroup(), KColorScheme::View);
QColor selected_bg = scheme.decoration(KColorScheme::FocusColor).color();
QColor hgh = KColorUtils::mix(QApplication::palette().window().color(), selected_bg, 0.2);
QColor hover_bg = scheme.decoration(KColorScheme::HoverColor).color();
QColor light_bg = scheme.shade(KColorScheme::LightShade);
QColor alt_bg = scheme.background(KColorScheme::NormalBackground).color();
QString stylesheet;
// effect background
stylesheet.append(QStringLiteral("QFrame#decoframe {border-bottom:2px solid "
"palette(mid);background: transparent} QFrame#decoframe[active=\"true\"] {background: %1;}")
.arg(hgh.name()));
// effect in group background
stylesheet.append(
QStringLiteral("QFrame#decoframesub {border-top:1px solid palette(light);} QFrame#decoframesub[active=\"true\"] {background: %1;}").arg(hgh.name()));
// group background
stylesheet.append(QStringLiteral("QFrame#decoframegroup {border:2px solid palette(dark);margin:0px;margin-top:2px;} "));
// effect title bar
stylesheet.append(QStringLiteral("QFrame#frame {margin-bottom:2px;} QFrame#frame[target=\"true\"] "
"{background: palette(highlight);}"));
// group effect title bar
stylesheet.append(QStringLiteral("QFrame#framegroup {background: palette(dark);} "
"QFrame#framegroup[target=\"true\"] {background: palette(highlight);} "));
// draggable effect bar content
stylesheet.append(QStringLiteral("QProgressBar::chunk:horizontal {background: palette(button);border-top-left-radius: 4px;border-bottom-left-radius: 4px;} "
"QProgressBar::chunk:horizontal#dragOnly {background: %1;border-top-left-radius: 4px;border-bottom-left-radius: 4px;} "
"QProgressBar::chunk:horizontal:hover {background: %2;}")
.arg(alt_bg.name(), selected_bg.name()));
// draggable effect bar
stylesheet.append(QStringLiteral("QProgressBar:horizontal {border: 1px solid palette(dark);border-top-left-radius: 4px;border-bottom-left-radius: "
"4px;border-right:0px;background:%3;padding: 0px;text-align:left center} QProgressBar:horizontal:disabled {border: 1px "
"solid palette(button)} QProgressBar:horizontal#dragOnly {background: %3} QProgressBar:horizontal[inTimeline=\"true\"] { "
"border: 1px solid %1;border-right: 0px;background: %2;padding: 0px;text-align:left center } "
"QProgressBar::chunk:horizontal[inTimeline=\"true\"] {background: %1;}")
.arg(hover_bg.name(), light_bg.name(), alt_bg.name()));
// spin box for draggable widget
stylesheet.append(
QStringLiteral("QAbstractSpinBox#dragBox {border: 1px solid palette(dark);border-top-right-radius: 4px;border-bottom-right-radius: "
"4px;padding-right:0px;} QAbstractSpinBox::down-button#dragBox {width:0px;padding:0px;} QAbstractSpinBox:disabled#dragBox {border: 1px "
"solid palette(button);} QAbstractSpinBox::up-button#dragBox {width:0px;padding:0px;} QAbstractSpinBox[inTimeline=\"true\"]#dragBox { "
"border: 1px solid %1;} QAbstractSpinBox:hover#dragBox {border: 1px solid %2;} ")
.arg(hover_bg.name(), selected_bg.name()));
// group editable labels
stylesheet.append(QStringLiteral("MyEditableLabel { background-color: transparent; color: palette(bright-text); border-radius: 2px;border: 1px solid "
"transparent;} MyEditableLabel:hover {border: 1px solid palette(highlight);} "));
// transparent qcombobox
stylesheet.append(QStringLiteral("QComboBox { background-color: transparent;} "));
return stylesheet;
}
void AssetPanel::processSplitEffect(bool enable)
{
ObjectType id = m_effectStackWidget->stackOwner().first;
if (id == ObjectType::TimelineClip) {
emit doSplitEffect(enable);
} else if (id == ObjectType::BinClip) {
emit doSplitBinEffect(enable);
}
}
void AssetPanel::showKeyframes(bool enable)
{
if (m_transitionWidget->isVisible()) {
pCore->showClipKeyframes(m_transitionWidget->stackOwner(), enable);
} else {
pCore->showClipKeyframes(m_effectStackWidget->stackOwner(), enable);
}
}
ObjectId AssetPanel::effectStackOwner()
{
if (m_transitionWidget->isVisible()) {
return m_transitionWidget->stackOwner();
}
if (!m_effectStackWidget->isVisible()) {
return ObjectId(ObjectType::NoItem, -1);
}
return m_effectStackWidget->stackOwner();
}
void AssetPanel::parameterChanged(QString name, int value)
{
Q_UNUSED(name)
emit changeSpeed(value);
}
bool AssetPanel::addEffect(const QString &effectId)
{
if (!m_effectStackWidget->isVisible()) {
return false;
}
return m_effectStackWidget->addEffect(effectId);
}
void AssetPanel::enableStack(bool enable)
{
if (!m_effectStackWidget->isVisible()) {
return;
}
m_effectStackWidget->enableStack(enable);
}
void AssetPanel::deleteCurrentEffect()
{
if (m_effectStackWidget->isVisible()) {
m_effectStackWidget->removeCurrentEffect();
}
}
diff --git a/src/assets/assetpanel.hpp b/src/assets/assetpanel.hpp
index b44f65749..3dce5c120 100644
--- a/src/assets/assetpanel.hpp
+++ b/src/assets/assetpanel.hpp
@@ -1,105 +1,105 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#ifndef ASSETPANEL_H
#define ASSETPANEL_H
#include
#include
#include
#include "definitions.h"
class KSqueezedTextLabel;
class KDualAction;
class QToolButton;
/** @brief This class is the widget that provides interaction with the asset currently selected.
That is, it either displays an effectStack or the parameters of a transition
*/
class AssetParameterModel;
class AssetParameterView;
class EffectStackModel;
class EffectStackView;
class TransitionStackView;
class QLabel;
class AssetPanel : public QWidget
{
Q_OBJECT
public:
AssetPanel(QWidget *parent);
/* @brief Shows the parameters of the given transition model */
- void showTransition(int tid, std::shared_ptr transition_model);
+ void showTransition(int tid, const std::shared_ptr &transition_model);
/* @brief Shows the parameters of the given effect stack model */
- void showEffectStack(const QString &itemName, std::shared_ptr effectsModel, QSize frameSize, bool showKeyframes);
+ void showEffectStack(const QString &itemName, const std::shared_ptr &effectsModel, QSize frameSize, bool showKeyframes);
/* @brief Clear the panel so that it doesn't display anything */
void clear();
/* @brief This method should be called when the style changes */
void updatePalette();
/* @brief Returns the object type / id of effectstack owner */
ObjectId effectStackOwner();
/* @brief Add an effect to the current stack owner */
bool addEffect(const QString &effectId);
public slots:
/** @brief Clear panel if displaying itemId */
void clearAssetPanel(int itemId);
void parameterChanged(QString name, int value);
void deleteCurrentEffect();
protected:
/** @brief Return the stylesheet used to display the panel (based on current palette). */
static const QString getStyleSheet();
QVBoxLayout *m_lay;
KSqueezedTextLabel *m_assetTitle;
QWidget *m_container;
TransitionStackView *m_transitionWidget;
EffectStackView *m_effectStackWidget;
private:
QToolButton *m_switchBuiltStack;
KDualAction *m_splitButton;
KDualAction *m_enableStackButton;
KDualAction *m_timelineButton;
private slots:
void processSplitEffect(bool enable);
/** Displays the owner clip keyframes in timeline */
void showKeyframes(bool enable);
/** Enable / disable effect stack */
void enableStack(bool enable);
signals:
void doSplitEffect(bool);
void doSplitBinEffect(bool);
void seekToPos(int);
void changeSpeed(int);
void reloadEffect(const QString &path);
};
#endif
diff --git a/src/assets/keyframes/model/corners/cornershelper.cpp b/src/assets/keyframes/model/corners/cornershelper.cpp
index 55ad8b5f4..83ec196f4 100644
--- a/src/assets/keyframes/model/corners/cornershelper.cpp
+++ b/src/assets/keyframes/model/corners/cornershelper.cpp
@@ -1,99 +1,99 @@
/*
Copyright (C) 2018 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 "cornershelper.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "core.h"
#include "gentime.h"
#include "monitor/monitor.h"
#include
-
+#include
CornersHelper::CornersHelper(Monitor *monitor, std::shared_ptr model, QPersistentModelIndex index, QObject *parent)
- : KeyframeMonitorHelper(monitor, model, index, parent)
+ : KeyframeMonitorHelper(monitor, std::move(model), std::move(index), parent)
{
}
void CornersHelper::slotUpdateFromMonitorData(const QVariantList &v)
{
const QVariantList points = QVariant(v).toList();
QSize frameSize = pCore->getCurrentFrameSize();
int ix = 0;
for (int i = 0; i < points.size(); i++) {
QPointF pt = points.at(i).toPointF();
double x = (pt.x() / frameSize.width() + 1) / 3;
double y = (pt.y() / frameSize.height() + 1) / 3;
emit updateKeyframeData(m_indexes.at(ix), x);
emit updateKeyframeData(m_indexes.at(ix + 1), y);
ix += 2;
}
}
void CornersHelper::refreshParams(int pos)
{
QVariantList points{QPointF(), QPointF(), QPointF(), QPointF()};
QList coords;
QSize frameSize = pCore->getCurrentFrameSize();
for (const auto &ix : m_indexes) {
ParamType type = m_model->data(ix, AssetParameterModel::TypeRole).value();
if (type != ParamType::KeyframeParam) {
continue;
}
int paramName = m_model->data(ix, AssetParameterModel::NameRole).toInt();
if (paramName > 7) {
continue;
}
double value = m_model->getKeyframeModel()->getInterpolatedValue(pos, ix).toDouble();
value = ((3 * value) - 1) * (paramName % 2 == 0 ? frameSize.width() : frameSize.height());
switch (paramName) {
case 0:
points[0] = QPointF(value, points.at(0).toPointF().y());
break;
case 1:
points[0] = QPointF(points.at(0).toPointF().x(), value);
break;
case 2:
points[1] = QPointF(value, points.at(1).toPointF().y());
break;
case 3:
points[1] = QPointF(points.at(1).toPointF().x(), value);
break;
case 4:
points[2] = QPointF(value, points.at(2).toPointF().y());
break;
case 5:
points[2] = QPointF(points.at(2).toPointF().x(), value);
break;
case 6:
points[3] = QPointF(value, points.at(3).toPointF().y());
break;
case 7:
points[3] = QPointF(points.at(3).toPointF().x(), value);
break;
default:
break;
}
}
if (m_monitor) {
m_monitor->setUpEffectGeometry(QRect(), points);
}
}
diff --git a/src/assets/keyframes/model/keyframemodel.cpp b/src/assets/keyframes/model/keyframemodel.cpp
index ba65767b7..bb9daa382 100644
--- a/src/assets/keyframes/model/keyframemodel.cpp
+++ b/src/assets/keyframes/model/keyframemodel.cpp
@@ -1,1215 +1,1216 @@
/***************************************************************************
* 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 "keyframemodel.hpp"
#include "core.h"
#include "doc/docundostack.hpp"
#include "macros.hpp"
#include "rotoscoping/bpoint.h"
#include "rotoscoping/rotohelper.hpp"
#include "profiles/profilemodel.hpp"
#include
#include
#include
+#include
KeyframeModel::KeyframeModel(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack, QObject *parent)
: QAbstractListModel(parent)
, m_model(std::move(model))
, m_undoStack(std::move(undo_stack))
, m_index(index)
, m_lastData()
, m_lock(QReadWriteLock::Recursive)
{
qDebug() << "Construct keyframemodel. Checking model:" << m_model.expired();
if (auto ptr = m_model.lock()) {
m_paramType = ptr->data(m_index, AssetParameterModel::TypeRole).value();
}
setup();
refresh();
}
void KeyframeModel::setup()
{
// We connect the signals of the abstractitemmodel to a more generic one.
connect(this, &KeyframeModel::columnsMoved, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::columnsRemoved, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::columnsInserted, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::rowsMoved, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::rowsRemoved, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::rowsInserted, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::modelReset, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::dataChanged, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
}
bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value, bool notify, Fun &undo, Fun &redo)
{
qDebug() << "ADD keyframe" << pos.frames(pCore->getCurrentFps()) << value << notify;
QWriteLocker locker(&m_lock);
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
if (m_keyframeList.count(pos) > 0) {
qDebug() << "already there";
if (std::pair({type, value}) == m_keyframeList.at(pos)) {
qDebug() << "nothing to do";
return true; // nothing to do
}
// In this case we simply change the type and value
KeyframeType oldType = m_keyframeList[pos].first;
QVariant oldValue = m_keyframeList[pos].second;
local_undo = updateKeyframe_lambda(pos, oldType, oldValue, notify);
local_redo = updateKeyframe_lambda(pos, type, value, notify);
} else {
local_redo = addKeyframe_lambda(pos, type, value, notify);
local_undo = deleteKeyframe_lambda(pos, notify);
}
if (local_redo()) {
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
return false;
}
bool KeyframeModel::addKeyframe(int frame, double normalizedValue)
{
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double realValue;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (normalizedValue >= 0.5) {
realValue = norm + (2 * (normalizedValue - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - normalizedValue), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (normalizedValue * (max - min) + min) / factor;
}
// TODO: Use default configurable kf type
return addKeyframe(GenTime(frame, pCore->getCurrentFps()), KeyframeType::Linear, realValue);
}
return false;
}
bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value)
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool update = (m_keyframeList.count(pos) > 0);
- bool res = addKeyframe(pos, type, value, true, undo, redo);
+ bool res = addKeyframe(pos, type, std::move(value), true, undo, redo);
if (res) {
PUSH_UNDO(undo, redo, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
}
return res;
}
bool KeyframeModel::removeKeyframe(GenTime pos, Fun &undo, Fun &redo, bool notify)
{
qDebug() << "Going to remove keyframe at " << pos.frames(pCore->getCurrentFps()) << " NOTIFY: " << notify;
qDebug() << "before" << getAnimProperty();
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType oldType = m_keyframeList[pos].first;
QVariant oldValue = m_keyframeList[pos].second;
Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, notify);
Fun local_redo = deleteKeyframe_lambda(pos, notify);
if (local_redo()) {
qDebug() << "after" << getAnimProperty();
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
return false;
}
bool KeyframeModel::removeKeyframe(int frame)
{
GenTime pos(frame, pCore->getCurrentFps());
return removeKeyframe(pos);
}
bool KeyframeModel::removeKeyframe(GenTime pos)
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
if (m_keyframeList.count(pos) > 0 && m_keyframeList.find(pos) == m_keyframeList.begin()) {
return false; // initial point must stay
}
bool res = removeKeyframe(pos, undo, redo);
if (res) {
PUSH_UNDO(undo, redo, i18n("Delete keyframe"));
}
return res;
}
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo)
{
qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps());
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(oldPos) > 0);
if (oldPos == pos) {
if (!newVal.isValid()) {
// no change
return true;
}
if (m_paramType == ParamType::AnimatedRect) {
return updateKeyframe(pos, newVal);
}
double realValue = newVal.toDouble();
// Calculate real value from normalized
if (auto ptr = m_model.lock()) {
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (realValue >= 0.5) {
realValue = norm + (2 * (realValue - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - realValue), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (realValue * (max - min) + min) / factor;
}
}
return updateKeyframe(pos, realValue);
}
KeyframeType oldType = m_keyframeList[oldPos].first;
QVariant oldValue = m_keyframeList[oldPos].second;
if (oldPos != pos && hasKeyframe(pos)) return false;
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
qDebug() << getAnimProperty();
// TODO: use the new Animation::key_set_frame to move a keyframe
bool res = removeKeyframe(oldPos, local_undo, local_redo);
qDebug() << "Move keyframe finished deletion:" << res;
qDebug() << getAnimProperty();
if (res) {
if (m_paramType == ParamType::AnimatedRect) {
if (!newVal.isValid()) {
newVal = oldValue;
}
res = addKeyframe(pos, oldType, newVal, true, local_undo, local_redo);
} else if (newVal.isValid()) {
if (auto ptr = m_model.lock()) {
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double realValue = newVal.toDouble();
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (newVal >= 0.5) {
realValue = norm + (2 * (realValue - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - realValue), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (realValue * (max - min) + min) / factor;
}
res = addKeyframe(pos, oldType, realValue, true, local_undo, local_redo);
}
} else {
res = addKeyframe(pos, oldType, oldValue, true, local_undo, local_redo);
}
qDebug() << "Move keyframe finished insertion:" << res;
qDebug() << getAnimProperty();
}
if (res) {
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
} else {
bool undone = local_undo();
Q_ASSERT(undone);
}
return res;
}
bool KeyframeModel::moveKeyframe(int oldPos, int pos, bool logUndo)
{
GenTime oPos(oldPos, pCore->getCurrentFps());
GenTime nPos(pos, pCore->getCurrentFps());
return moveKeyframe(oPos, nPos, QVariant(), logUndo);
}
bool KeyframeModel::offsetKeyframes(int oldPos, int pos, bool logUndo)
{
if (oldPos == pos) return true;
GenTime oldFrame(oldPos, pCore->getCurrentFps());
Q_ASSERT(m_keyframeList.count(oldFrame) > 0);
GenTime diff(pos - oldPos, pCore->getCurrentFps());
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
QList times;
for (const auto &m : m_keyframeList) {
if (m.first < oldFrame) continue;
times << m.first;
}
bool res = true;
for (const auto &t : times) {
res &= moveKeyframe(t, t + diff, QVariant(), undo, redo);
}
if (res && logUndo) {
PUSH_UNDO(undo, redo, i18n("Move keyframes"));
}
return res;
}
bool KeyframeModel::moveKeyframe(int oldPos, int pos, QVariant newVal)
{
GenTime oPos(oldPos, pCore->getCurrentFps());
GenTime nPos(pos, pCore->getCurrentFps());
- return moveKeyframe(oPos, nPos, newVal, true);
+ return moveKeyframe(oPos, nPos, std::move(newVal), true);
}
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, bool logUndo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(oldPos) > 0);
if (oldPos == pos) return true;
Fun undo = []() { return true; };
Fun redo = []() { return true; };
- bool res = moveKeyframe(oldPos, pos, newVal, undo, redo);
+ bool res = moveKeyframe(oldPos, pos, std::move(newVal), undo, redo);
if (res && logUndo) {
PUSH_UNDO(undo, redo, i18n("Move keyframe"));
}
return res;
}
bool KeyframeModel::directUpdateKeyframe(GenTime pos, QVariant value)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType type = m_keyframeList[pos].first;
- auto operation = updateKeyframe_lambda(pos, type, value, true);
+ auto operation = updateKeyframe_lambda(pos, type, std::move(value), true);
return operation();
}
-bool KeyframeModel::updateKeyframe(GenTime pos, QVariant value, Fun &undo, Fun &redo, bool update)
+bool KeyframeModel::updateKeyframe(GenTime pos, const QVariant &value, Fun &undo, Fun &redo, bool update)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType type = m_keyframeList[pos].first;
QVariant oldValue = m_keyframeList[pos].second;
// Check if keyframe is different
if (m_paramType == ParamType::KeyframeParam) {
if (qFuzzyCompare(oldValue.toDouble(), value.toDouble())) return true;
}
auto operation = updateKeyframe_lambda(pos, type, value, update);
auto reverse = updateKeyframe_lambda(pos, type, oldValue, update);
bool res = operation();
if (res) {
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return res;
}
bool KeyframeModel::updateKeyframe(int pos, double newVal)
{
GenTime Pos(pos, pCore->getCurrentFps());
if (auto ptr = m_model.lock()) {
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double realValue;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (newVal >= 0.5) {
realValue = norm + (2 * (newVal - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (newVal * (max - min) + min) / factor;
}
return updateKeyframe(Pos, realValue);
}
return false;
}
bool KeyframeModel::updateKeyframe(GenTime pos, QVariant value)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
- bool res = updateKeyframe(pos, value, undo, redo);
+ bool res = updateKeyframe(pos, std::move(value), undo, redo);
if (res) {
PUSH_UNDO(undo, redo, i18n("Update keyframe"));
}
return res;
}
KeyframeType convertFromMltType(mlt_keyframe_type type)
{
switch (type) {
case mlt_keyframe_linear:
return KeyframeType::Linear;
case mlt_keyframe_discrete:
return KeyframeType::Discrete;
case mlt_keyframe_smooth:
return KeyframeType::Curve;
}
return KeyframeType::Linear;
}
bool KeyframeModel::updateKeyframeType(GenTime pos, int type, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType oldType = m_keyframeList[pos].first;
KeyframeType newType = convertFromMltType((mlt_keyframe_type)type);
QVariant value = m_keyframeList[pos].second;
// Check if keyframe is different
if (m_paramType == ParamType::KeyframeParam) {
if (oldType == newType) return true;
}
auto operation = updateKeyframe_lambda(pos, newType, value, true);
auto reverse = updateKeyframe_lambda(pos, oldType, value, true);
bool res = operation();
if (res) {
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return res;
}
-Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, QVariant value, bool notify)
+Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, const QVariant &value, bool notify)
{
QWriteLocker locker(&m_lock);
return [this, pos, type, value, notify]() {
qDebug() << "update lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
Q_ASSERT(m_keyframeList.count(pos) > 0);
int row = static_cast(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
m_keyframeList[pos].first = type;
m_keyframeList[pos].second = value;
if (notify) emit dataChanged(index(row), index(row), {ValueRole, NormalizedValueRole, TypeRole});
return true;
};
}
-Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, QVariant value, bool notify)
+Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, const QVariant &value, bool notify)
{
QWriteLocker locker(&m_lock);
return [this, notify, pos, type, value]() {
qDebug() << "add lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
Q_ASSERT(m_keyframeList.count(pos) == 0);
// We determine the row of the newly added marker
auto insertionIt = m_keyframeList.lower_bound(pos);
int insertionRow = static_cast(m_keyframeList.size());
if (insertionIt != m_keyframeList.end()) {
insertionRow = static_cast(std::distance(m_keyframeList.begin(), insertionIt));
}
if (notify) beginInsertRows(QModelIndex(), insertionRow, insertionRow);
m_keyframeList[pos].first = type;
m_keyframeList[pos].second = value;
if (notify) endInsertRows();
return true;
};
}
Fun KeyframeModel::deleteKeyframe_lambda(GenTime pos, bool notify)
{
QWriteLocker locker(&m_lock);
return [this, pos, notify]() {
qDebug() << "delete lambda" << pos.frames(pCore->getCurrentFps()) << notify;
qDebug() << "before" << getAnimProperty();
Q_ASSERT(m_keyframeList.count(pos) > 0);
Q_ASSERT(pos != GenTime()); // cannot delete initial point
int row = static_cast(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
if (notify) beginRemoveRows(QModelIndex(), row, row);
m_keyframeList.erase(pos);
if (notify) endRemoveRows();
qDebug() << "after" << getAnimProperty();
return true;
};
}
QHash KeyframeModel::roleNames() const
{
QHash roles;
roles[PosRole] = "position";
roles[FrameRole] = "frame";
roles[TypeRole] = "type";
roles[ValueRole] = "value";
roles[NormalizedValueRole] = "normalizedValue";
return roles;
}
QVariant KeyframeModel::data(const QModelIndex &index, int role) const
{
READ_LOCK();
if (index.row() < 0 || index.row() >= static_cast(m_keyframeList.size()) || !index.isValid()) {
return QVariant();
}
auto it = m_keyframeList.begin();
std::advance(it, index.row());
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
case ValueRole:
return it->second.second;
case NormalizedValueRole: {
if (m_paramType == ParamType::AnimatedRect) {
const QString &data = it->second.second.toString();
QLocale locale;
return locale.toDouble(data.section(QLatin1Char(' '), -1));
}
double val = it->second.second.toDouble();
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double linear = val * factor;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (linear >= norm) {
return 0.5 + (linear - norm) / (max * factor - norm) * 0.5;
}
// transform current value to 0..1 scale
double scaled = (linear - norm) / (min * factor - norm);
// Log scale
return 0.5 - pow(scaled, 0.6) * 0.5;
}
return (linear - min) / (max - min);
} else {
qDebug() << "// CANNOT LOCK effect MODEL";
}
return 1;
}
case PosRole:
return it->first.seconds();
case FrameRole:
case Qt::UserRole:
return it->first.frames(pCore->getCurrentFps());
case TypeRole:
return QVariant::fromValue(it->second.first);
}
return QVariant();
}
int KeyframeModel::rowCount(const QModelIndex &parent) const
{
READ_LOCK();
if (parent.isValid()) return 0;
return static_cast(m_keyframeList.size());
}
bool KeyframeModel::singleKeyframe() const
{
READ_LOCK();
return m_keyframeList.size() <= 1;
}
Keyframe KeyframeModel::getKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
if (m_keyframeList.count(pos) <= 0) {
// return empty marker
*ok = false;
return {GenTime(), KeyframeType::Linear};
}
*ok = true;
return {pos, m_keyframeList.at(pos).first};
}
Keyframe KeyframeModel::getNextKeyframe(const GenTime &pos, bool *ok) const
{
auto it = m_keyframeList.upper_bound(pos);
if (it == m_keyframeList.end()) {
// return empty marker
*ok = false;
return {GenTime(), KeyframeType::Linear};
}
*ok = true;
return {(*it).first, (*it).second.first};
}
Keyframe KeyframeModel::getPrevKeyframe(const GenTime &pos, bool *ok) const
{
auto it = m_keyframeList.lower_bound(pos);
if (it == m_keyframeList.begin()) {
// return empty marker
*ok = false;
return {GenTime(), KeyframeType::Linear};
}
--it;
*ok = true;
return {(*it).first, (*it).second.first};
}
Keyframe KeyframeModel::getClosestKeyframe(const GenTime &pos, bool *ok) const
{
if (m_keyframeList.count(pos) > 0) {
return getKeyframe(pos, ok);
}
bool ok1, ok2;
auto next = getNextKeyframe(pos, &ok1);
auto prev = getPrevKeyframe(pos, &ok2);
*ok = ok1 || ok2;
if (ok1 && ok2) {
double fps = pCore->getCurrentFps();
if (qAbs(next.first.frames(fps) - pos.frames(fps)) < qAbs(prev.first.frames(fps) - pos.frames(fps))) {
return next;
}
return prev;
} else if (ok1) {
return next;
} else if (ok2) {
return prev;
}
// return empty marker
return {GenTime(), KeyframeType::Linear};
}
bool KeyframeModel::hasKeyframe(int frame) const
{
return hasKeyframe(GenTime(frame, pCore->getCurrentFps()));
}
bool KeyframeModel::hasKeyframe(const GenTime &pos) const
{
READ_LOCK();
return m_keyframeList.count(pos) > 0;
}
bool KeyframeModel::removeAllKeyframes(Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
std::vector all_pos;
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
int kfrCount = (int)m_keyframeList.size() - 1;
// we trigger only one global remove/insertrow event
Fun update_redo_start = [this, kfrCount]() {
beginRemoveRows(QModelIndex(), 1, kfrCount);
return true;
};
Fun update_redo_end = [this]() {
endRemoveRows();
return true;
};
Fun update_undo_start = [this, kfrCount]() {
beginInsertRows(QModelIndex(), 1, kfrCount);
return true;
};
Fun update_undo_end = [this]() {
endInsertRows();
return true;
};
PUSH_LAMBDA(update_redo_start, local_redo);
PUSH_LAMBDA(update_undo_start, local_undo);
for (const auto &m : m_keyframeList) {
all_pos.push_back(m.first);
}
update_redo_start();
bool res = true;
bool first = true;
for (const auto &p : all_pos) {
if (first) { // skip first point
first = false;
continue;
}
res = removeKeyframe(p, local_undo, local_redo, false);
if (!res) {
bool undone = local_undo();
Q_ASSERT(undone);
return false;
}
}
update_redo_end();
PUSH_LAMBDA(update_redo_end, local_redo);
PUSH_LAMBDA(update_undo_end, local_undo);
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
bool KeyframeModel::removeAllKeyframes()
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = removeAllKeyframes(undo, redo);
if (res) {
PUSH_UNDO(undo, redo, i18n("Delete all keyframes"));
}
return res;
}
mlt_keyframe_type convertToMltType(KeyframeType type)
{
switch (type) {
case KeyframeType::Linear:
return mlt_keyframe_linear;
case KeyframeType::Discrete:
return mlt_keyframe_discrete;
case KeyframeType::Curve:
return mlt_keyframe_smooth;
}
return mlt_keyframe_linear;
}
QString KeyframeModel::getAnimProperty() const
{
if (m_paramType == ParamType::Roto_spline) {
return getRotoProperty();
}
Mlt::Properties mlt_prop;
if (auto ptr = m_model.lock()) {
ptr->passProperties(mlt_prop);
}
int ix = 0;
bool first = true;
std::shared_ptr anim;
- for (const auto keyframe : m_keyframeList) {
+ for (const auto &keyframe : m_keyframeList) {
if (first) {
switch (m_paramType) {
case ParamType::AnimatedRect:
mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps()));
break;
default:
mlt_prop.anim_set("key", keyframe.second.second.toDouble(), keyframe.first.frames(pCore->getCurrentFps()));
break;
}
anim.reset(mlt_prop.get_anim("key"));
anim->key_set_type(ix, convertToMltType(keyframe.second.first));
first = false;
ix++;
continue;
}
switch (m_paramType) {
case ParamType::AnimatedRect:
mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps()));
break;
default:
mlt_prop.anim_set("key", keyframe.second.second.toDouble(), keyframe.first.frames(pCore->getCurrentFps()));
break;
}
anim->key_set_type(ix, convertToMltType(keyframe.second.first));
ix++;
}
char *cut = anim->serialize_cut();
QString ret(cut);
free(cut);
return ret;
}
QString KeyframeModel::getRotoProperty() const
{
QJsonDocument doc;
if (auto ptr = m_model.lock()) {
int in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
int out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
QMap map;
- for (const auto keyframe : m_keyframeList) {
+ for (const auto &keyframe : m_keyframeList) {
map.insert(QString::number(in + keyframe.first.frames(pCore->getCurrentFps())).rightJustified(log10((double)out) + 1, '0'), keyframe.second.second);
}
doc = QJsonDocument::fromVariant(QVariant(map));
}
return doc.toJson();
}
void KeyframeModel::parseAnimProperty(const QString &prop)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
QLocale locale;
disconnect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
removeAllKeyframes(undo, redo);
int in = 0;
int out = 0;
Mlt::Properties mlt_prop;
if (auto ptr = m_model.lock()) {
in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
ptr->passProperties(mlt_prop);
}
mlt_prop.set("key", prop.toUtf8().constData());
// This is a fake query to force the animation to be parsed
(void)mlt_prop.anim_get_double("key", 0, out);
Mlt::Animation anim = mlt_prop.get_animation("key");
qDebug() << "Found" << anim.key_count() << ", OUT: "< in) {
// Always add a keyframe at start pos
addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo);
} else if (frame == in && hasKeyframe(GenTime(in))) {
// First keyframe already exists, adjust its value
updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, true);
continue;
}
addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo);
}
connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
}
void KeyframeModel::resetAnimProperty(const QString &prop)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
// Delete all existing keyframes
disconnect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
removeAllKeyframes(undo, redo);
Mlt::Properties mlt_prop;
QLocale locale;
int in = 0;
if (auto ptr = m_model.lock()) {
in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
ptr->passProperties(mlt_prop);
}
mlt_prop.set("key", prop.toUtf8().constData());
// This is a fake query to force the animation to be parsed
(void)mlt_prop.anim_get_int("key", 0, 0);
Mlt::Animation anim = mlt_prop.get_animation("key");
qDebug() << "Found" << anim.key_count() << "animation properties";
for (int i = 0; i < anim.key_count(); ++i) {
int frame;
mlt_keyframe_type type;
anim.key_get(i, frame, type);
if (!prop.contains(QLatin1Char('='))) {
// TODO: use a default user defined type
type = mlt_keyframe_linear;
}
QVariant value;
switch (m_paramType) {
case ParamType::AnimatedRect: {
mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(locale.toString(rect.o)));
break;
}
default:
value = QVariant(mlt_prop.anim_get_double("key", frame));
break;
}
if (i == 0 && frame > in) {
// Always add a keyframe at start pos
addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
} else if (frame == in && hasKeyframe(GenTime(in))) {
// First keyframe already exists, adjust its value
updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, false);
continue;
}
addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
}
QString effectName;
if (auto ptr = m_model.lock()) {
effectName = ptr->data(m_index, Qt::DisplayRole).toString();
} else {
effectName = i18n("effect");
}
Fun update_local = [this]() {
emit dataChanged(index(0), index((int)m_keyframeList.size()), {});
return true;
};
update_local();
PUSH_LAMBDA(update_local, undo);
PUSH_LAMBDA(update_local, redo);
PUSH_UNDO(undo, redo, i18n("Reset %1", effectName));
connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
}
void KeyframeModel::parseRotoProperty(const QString &prop)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(prop.toLatin1(), &jsonError);
QVariant data = doc.toVariant();
if (data.canConvert(QVariant::Map)) {
QList keyframes;
QMap map = data.toMap();
QMap::const_iterator i = map.constBegin();
while (i != map.constEnd()) {
addKeyframe(GenTime(i.key().toInt(), pCore->getCurrentFps()), KeyframeType::Linear, i.value(), false, undo, redo);
++i;
}
}
}
QVariant KeyframeModel::getInterpolatedValue(int p) const
{
auto pos = GenTime(p, pCore->getCurrentFps());
return getInterpolatedValue(pos);
}
-QVariant KeyframeModel::updateInterpolated(QVariant interpValue, double val)
+QVariant KeyframeModel::updateInterpolated(const QVariant &interpValue, double val)
{
QStringList vals = interpValue.toString().split(QLatin1Char(' '));
QLocale locale;
if (!vals.isEmpty()) {
vals[vals.size() - 1] = locale.toString(val);
}
return vals.join(QLatin1Char(' '));
}
QVariant KeyframeModel::getNormalizedValue(double newVal) const
{
if (auto ptr = m_model.lock()) {
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double realValue;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (newVal >= 0.5) {
realValue = norm + (2 * (newVal - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (newVal * (max - min) + min) / factor;
}
return QVariant(realValue);
}
return QVariant();
}
QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const
{
if (m_keyframeList.count(pos) > 0) {
return m_keyframeList.at(pos).second;
}
if (m_keyframeList.size() == 0) {
return QVariant();
}
auto next = m_keyframeList.upper_bound(pos);
if (next == m_keyframeList.cbegin()) {
return (m_keyframeList.cbegin())->second.second;
} else if (next == m_keyframeList.cend()) {
auto it = m_keyframeList.cend();
--it;
return it->second.second;
}
auto prev = next;
--prev;
// We now have surrounding keyframes, we use mlt to compute the value
Mlt::Properties prop;
if (auto ptr = m_model.lock()) {
ptr->passProperties(prop);
}
QLocale locale;
int p = pos.frames(pCore->getCurrentFps());
if (m_paramType == ParamType::KeyframeParam) {
prop.anim_set("keyframe", prev->second.second.toDouble(), prev->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
convertToMltType(prev->second.first));
prop.anim_set("keyframe", next->second.second.toDouble(), next->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
convertToMltType(next->second.first));
return QVariant(prop.anim_get_double("keyframe", p));
} else if (m_paramType == ParamType::AnimatedRect) {
QStringList vals = prev->second.second.toString().split(QLatin1Char(' '));
if (vals.count() >= 4) {
mlt_rect rect;
rect.x = vals.at(0).toInt();
rect.y = vals.at(1).toInt();
rect.w = vals.at(2).toInt();
rect.h = vals.at(3).toInt();
if (vals.count() > 4) {
rect.o = locale.toDouble(vals.at(4));
} else {
rect.o = 1;
}
prop.anim_set("keyframe", rect, prev->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
convertToMltType(prev->second.first));
}
vals = next->second.second.toString().split(QLatin1Char(' '));
if (vals.count() >= 4) {
mlt_rect rect;
rect.x = vals.at(0).toInt();
rect.y = vals.at(1).toInt();
rect.w = vals.at(2).toInt();
rect.h = vals.at(3).toInt();
if (vals.count() > 4) {
rect.o = locale.toDouble(vals.at(4));
} else {
rect.o = 1;
}
prop.anim_set("keyframe", rect, next->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
convertToMltType(next->second.first));
}
mlt_rect rect = prop.anim_get_rect("keyframe", p);
const QString res = QStringLiteral("%1 %2 %3 %4 %5").arg((int)rect.x).arg((int)rect.y).arg((int)rect.w).arg((int)rect.h).arg(locale.toString(rect.o));
return QVariant(res);
} else if (m_paramType == ParamType::Roto_spline) {
// interpolate
QSize frame = pCore->getCurrentFrameSize();
QList p1 = RotoHelper::getPoints(prev->second.second, frame);
qreal relPos = (p - prev->first.frames(pCore->getCurrentFps())) / (qreal)(((next->first - prev->first).frames(pCore->getCurrentFps())) + 1);
QList p2 = RotoHelper::getPoints(next->second.second, frame);
int count = qMin(p1.count(), p2.count());
QList vlist;
for (int i = 0; i < count; ++i) {
BPoint bp;
QList pl;
for (int j = 0; j < 3; ++j) {
if (p1.at(i)[j] != p2.at(i)[j]) {
bp[j] = QLineF(p1.at(i)[j], p2.at(i)[j]).pointAt(relPos);
} else {
bp[j] = p1.at(i)[j];
}
pl << QVariant(QList() << QVariant(bp[j].x() / frame.width()) << QVariant(bp[j].y() / frame.height()));
}
vlist << QVariant(pl);
}
return vlist;
}
return QVariant();
}
void KeyframeModel::sendModification()
{
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
QString name = ptr->data(m_index, AssetParameterModel::NameRole).toString();
if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect || m_paramType == ParamType::Roto_spline) {
m_lastData = getAnimProperty();
ptr->setParameter(name, m_lastData, false);
} else {
Q_ASSERT(false); // Not implemented, TODO
}
}
}
void KeyframeModel::refresh()
{
Q_ASSERT(m_index.isValid());
QString animData;
if (auto ptr = m_model.lock()) {
animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
} else {
qDebug() << "WARNING : unable to access keyframe's model";
return;
}
if (animData == m_lastData) {
// nothing to do
qDebug() << "// DATA WAS ALREADY PARSED, ABORTING REFRESH\n_________________";
return;
}
if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) {
parseAnimProperty(animData);
} else if (m_paramType == ParamType::Roto_spline) {
parseRotoProperty(animData);
} else {
// first, try to convert to double
bool ok = false;
double value = animData.toDouble(&ok);
if (ok) {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo);
} else {
Q_ASSERT(false); // Not implemented, TODO
}
}
m_lastData = animData;
}
void KeyframeModel::reset()
{
Q_ASSERT(m_index.isValid());
QString animData;
if (auto ptr = m_model.lock()) {
animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
} else {
qDebug() << "WARNING : unable to access keyframe's model";
return;
}
if (animData == m_lastData) {
// nothing to do
qDebug() << "// DATA WAS ALREADY PARSED, ABORTING\n_________________";
return;
}
if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) {
qDebug() << "parsing keyframe" << animData;
resetAnimProperty(animData);
} else if (m_paramType == ParamType::Roto_spline) {
// TODO: resetRotoProperty(animData);
} else {
// first, try to convert to double
bool ok = false;
double value = animData.toDouble(&ok);
if (ok) {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo);
PUSH_UNDO(undo, redo, i18n("Reset effect"));
qDebug() << "KEYFRAME ADDED" << value;
} else {
Q_ASSERT(false); // Not implemented, TODO
}
}
m_lastData = animData;
}
-QList KeyframeModel::getRanges(const QString &animData, std::shared_ptr model)
+QList KeyframeModel::getRanges(const QString &animData, const std::shared_ptr &model)
{
Mlt::Properties mlt_prop;
model->passProperties(mlt_prop);
QLocale locale;
mlt_prop.set("key", animData.toUtf8().constData());
// This is a fake query to force the animation to be parsed
(void)mlt_prop.anim_get_int("key", 0, 0);
Mlt::Animation anim = mlt_prop.get_animation("key");
int frame;
mlt_keyframe_type type;
anim.key_get(0, frame, type);
mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
QPoint pX(rect.x, rect.x);
QPoint pY(rect.y, rect.y);
QPoint pW(rect.w, rect.w);
QPoint pH(rect.h, rect.h);
QPoint pO(rect.o, rect.o);
for (int i = 1; i < anim.key_count(); ++i) {
anim.key_get(i, frame, type);
if (!animData.contains(QLatin1Char('='))) {
// TODO: use a default user defined type
type = mlt_keyframe_linear;
}
rect = mlt_prop.anim_get_rect("key", frame);
pX.setX(qMin((int)rect.x, pX.x()));
pX.setY(qMax((int)rect.x, pX.y()));
pY.setX(qMin((int)rect.y, pY.x()));
pY.setY(qMax((int)rect.y, pY.y()));
pW.setX(qMin((int)rect.w, pW.x()));
pW.setY(qMax((int)rect.w, pW.y()));
pH.setX(qMin((int)rect.h, pH.x()));
pH.setY(qMax((int)rect.h, pH.y()));
pO.setX(qMin((int)rect.o, pO.x()));
pO.setY(qMax((int)rect.o, pO.y()));
// value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(locale.toString(rect.o)));
}
QList result{pX, pY, pW, pH, pO};
return result;
}
std::shared_ptr KeyframeModel::getAnimation(const QString &animData)
{
std::shared_ptr mlt_prop(new Mlt::Properties());
mlt_prop->set("key", animData.toUtf8().constData());
// This is a fake query to force the animation to be parsed
(void)mlt_prop->anim_get_rect("key", 0, 0);
return mlt_prop;
}
QList KeyframeModel::getKeyframePos() const
{
QList all_pos;
for (const auto &m : m_keyframeList) {
all_pos.push_back(m.first);
}
return all_pos;
}
bool KeyframeModel::removeNextKeyframes(GenTime pos, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
std::vector all_pos;
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
int firstPos = 0;
for (const auto &m : m_keyframeList) {
if (m.first <= pos) {
firstPos++;
continue;
}
all_pos.push_back(m.first);
}
int kfrCount = (int)all_pos.size();
// we trigger only one global remove/insertrow event
Fun update_redo_start = [this, firstPos, kfrCount]() {
beginRemoveRows(QModelIndex(), firstPos, kfrCount);
return true;
};
Fun update_redo_end = [this]() {
endRemoveRows();
return true;
};
Fun update_undo_start = [this, firstPos, kfrCount]() {
beginInsertRows(QModelIndex(), firstPos, kfrCount);
return true;
};
Fun update_undo_end = [this]() {
endInsertRows();
return true;
};
PUSH_LAMBDA(update_redo_start, local_redo);
PUSH_LAMBDA(update_undo_start, local_undo);
update_redo_start();
bool res = true;
for (const auto &p : all_pos) {
res = removeKeyframe(p, local_undo, local_redo, false);
if (!res) {
bool undone = local_undo();
Q_ASSERT(undone);
return false;
}
}
update_redo_end();
PUSH_LAMBDA(update_redo_end, local_redo);
PUSH_LAMBDA(update_undo_end, local_undo);
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
diff --git a/src/assets/keyframes/model/keyframemodel.hpp b/src/assets/keyframes/model/keyframemodel.hpp
index 05cca0fca..b6c6f3052 100644
--- a/src/assets/keyframes/model/keyframemodel.hpp
+++ b/src/assets/keyframes/model/keyframemodel.hpp
@@ -1,220 +1,220 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#ifndef KEYFRAMELISTMODEL_H
#define KEYFRAMELISTMODEL_H
#include "assets/model/assetparametermodel.hpp"
#include "definitions.h"
#include "gentime.h"
#include "undohelper.hpp"
#include
#include
#include