diff --git a/fuzzer/fuzzing.cpp b/fuzzer/fuzzing.cpp index 72eb01d63..e9b48ea68 100644 --- a/fuzzer/fuzzing.cpp +++ b/fuzzer/fuzzing.cpp @@ -1,458 +1,493 @@ /*************************************************************************** * 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(); Logger::clear(); 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, all_groups; 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] = {}; all_groups[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 &groups = all_groups[timeline]; groups.clear(); for (int c : timeline->m_allGroups) { groups.push_back(c); } std::sort(groups.begin(), groups.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_group = [&](std::shared_ptr timeline) { int id = 0; ss >> id; if (!timeline) return -1; if (timeline->isGroup(id)) return id; if (all_timelines.size() == 0) return -1; if (all_groups.count(timeline) == 0) return -1; if (all_groups[timeline].size() == 0) return -1; id = modulo(id, (int)all_groups[timeline].size()); return all_groups[timeline][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; + 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_ClipModel") { + auto timeline = get_timeline(); + int id = 0, state_id; + double speed = 1; + PlaylistState::ClipState state = PlaylistState::VideoOnly; + std::string binId; + ss >> binId >> id >> state_id >> speed; + state = static_cast(state_id); + if (timeline) { + ClipModel::construct(timeline, QString::fromStdString(binId), -1, state, speed); + } } 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; + std::cout << "executing " << c << std::endl; rttr::type target_type = rttr::type::get(); bool found = false; - for (const std::string &t : {"TimelineModel"}) { + for (const std::string &t : {"TimelineModel", "TimelineFunctions"}) { rttr::type current_type = rttr::type::get_by_name(t); - // std::cout << "type " << t << " has methods count=" << current_type.get_methods().size() << std::endl; + 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) { + std::cout << "found!" << std::endl; 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; + 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.emplace_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.emplace_back(clipId); - // std::cout << "got clipId" << clipId << std::endl; + 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.emplace_back(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.emplace_back(itemId); // std::cout << "got itemId" << itemId << std::endl; } else if (arg_name == "groupId") { std::shared_ptr tim = (ptr.can_convert>() ? ptr.convert>() : nullptr); int groupId = get_group(tim); valid = valid && (groupId >= 0); arguments.emplace_back(groupId); // std::cout << "got clipId" << clipId << std::endl; } else if (arg_name == "logUndo") { bool a = false; ss >> a; // we enforce undo logging a = true; arguments.emplace_back(a); } else if (arg_name == "itemIds") { 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.emplace_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; + std::cout << "read int " << a << std::endl; + arguments.emplace_back(a); + } else if (arg_type == rttr::type::get()) { + size_t a = 0; + ss >> a; + arguments.emplace_back(a); + } else if (arg_type == rttr::type::get()) { + double a = 0; + ss >> a; + arguments.emplace_back(a); + } else if (arg_type == rttr::type::get()) { + float a = 0; + ss >> a; arguments.emplace_back(a); } else if (arg_type == rttr::type::get()) { bool a = false; ss >> a; // std::cout << "read bool " << a << std::endl; arguments.emplace_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.emplace_back(QString::fromStdString(str)); + } else if (arg_type == rttr::type::get>()) { + auto timeline = get_timeline(); + if (timeline) { + std::cout << "got timeline" << std::endl; + auto timeline2 = std::dynamic_pointer_cast(timeline); + arguments.emplace_back(timeline2); + ptr = timeline; + } else { + std::cout << "didn't get timeline" << std::endl; + valid = false; + } } 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 { std::cout << "ERROR: unsupported arg type " << arg_type.get_name().to_string() << std::endl; assert(false); } } else { if (p.get_type() == rttr::type::get()) { arguments.emplace_back(-1); } else { assert(false); } } } if (valid) { - // std::cout << "VALID!!!" << std::endl; + std::cout << "VALID!!! " << target_method.get_name().to_string() << std::endl; std::vector args; args.reserve(arguments.size()); - for (const auto &a : arguments) { + for (auto &a : arguments) { args.emplace_back(a); - // std::cout<<"argument="<checkConsistency()); } } } all_clips.clear(); all_tracks.clear(); all_compositions.clear(); all_groups.clear(); for (auto &all_timeline : all_timelines) { all_timeline.reset(); } pCore->m_projectManager = nullptr; Core::m_self.reset(); MltConnection::m_self.reset(); std::cout << "---------------------------------------------------------------------------------------------------------------------------------------------" "---------------" << std::endl; } diff --git a/src/definitions.cpp b/src/definitions.cpp index f46f52edb..b970b2249 100644 --- a/src/definitions.cpp +++ b/src/definitions.cpp @@ -1,200 +1,205 @@ /*************************************************************************** * Copyright (C) 2007 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 "definitions.h" #include #include #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 RTTR_REGISTRATION { using namespace rttr; // clang-format off registration::enumeration("GroupType")( value("Normal", GroupType::Normal), value("Selection", GroupType::Selection), value("AVSplit", GroupType::AVSplit), value("Leaf", GroupType::Leaf) ); + registration::enumeration("PlaylistState")( + value("VideoOnly", PlaylistState::VideoOnly), + value("AudioOnly", PlaylistState::AudioOnly), + value("Disabled", PlaylistState::Disabled) + ); // clang-format on } QDebug operator<<(QDebug qd, const ItemInfo &info) { qd << "ItemInfo " << &info; qd << "\tTrack" << info.track; qd << "\tStart pos: " << info.startPos.toString(); qd << "\tEnd pos: " << info.endPos.toString(); qd << "\tCrop start: " << info.cropStart.toString(); qd << "\tCrop duration: " << info.cropDuration.toString(); return qd.maybeSpace(); } CommentedTime::CommentedTime() : m_time(GenTime(0)) { } CommentedTime::CommentedTime(const GenTime &time, QString comment, int markerType) : m_time(time) , m_comment(std::move(comment)) , m_type(markerType) { } CommentedTime::CommentedTime(const QString &hash, const GenTime &time) : m_time(time) , m_comment(hash.section(QLatin1Char(':'), 1)) , m_type(hash.section(QLatin1Char(':'), 0, 0).toInt()) { } QString CommentedTime::comment() const { return (m_comment.isEmpty() ? i18n("Marker") : m_comment); } GenTime CommentedTime::time() const { return m_time; } void CommentedTime::setComment(const QString &comm) { m_comment = comm; } void CommentedTime::setMarkerType(int newtype) { m_type = newtype; } QString CommentedTime::hash() const { return QString::number(m_type) + QLatin1Char(':') + (m_comment.isEmpty() ? i18n("Marker") : m_comment); } int CommentedTime::markerType() const { return m_type; } QColor CommentedTime::markerColor(int type) { switch (type) { case 0: return Qt::red; break; case 1: return Qt::blue; break; case 2: return Qt::green; break; case 3: return Qt::yellow; break; default: return Qt::cyan; break; } } bool CommentedTime::operator>(const CommentedTime &op) const { return m_time > op.time(); } bool CommentedTime::operator<(const CommentedTime &op) const { return m_time < op.time(); } bool CommentedTime::operator>=(const CommentedTime &op) const { return m_time >= op.time(); } bool CommentedTime::operator<=(const CommentedTime &op) const { return m_time <= op.time(); } bool CommentedTime::operator==(const CommentedTime &op) const { return m_time == op.time(); } bool CommentedTime::operator!=(const CommentedTime &op) const { return m_time != op.time(); } const QString groupTypeToStr(GroupType t) { switch (t) { case GroupType::Normal: return QStringLiteral("Normal"); case GroupType::Selection: return QStringLiteral("Selection"); case GroupType::AVSplit: return QStringLiteral("AVSplit"); case GroupType::Leaf: return QStringLiteral("Leaf"); } Q_ASSERT(false); return QString(); } GroupType groupTypeFromStr(const QString &s) { std::vector types{GroupType::Selection, GroupType::Normal, GroupType::AVSplit, GroupType::Leaf}; for (const auto &t : types) { if (s == groupTypeToStr(t)) { return t; } } Q_ASSERT(false); return GroupType::Normal; } std::pair stateToBool(PlaylistState::ClipState state) { return {state == PlaylistState::VideoOnly, state == PlaylistState::AudioOnly}; } PlaylistState::ClipState stateFromBool(std::pair av) { assert(!av.first || !av.second); if (av.first) { return PlaylistState::VideoOnly; } else if (av.second) { return PlaylistState::AudioOnly; } else { return PlaylistState::Disabled; } } diff --git a/src/logger.cpp b/src/logger.cpp index 8e6ece04d..f34ef9c3a 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -1,387 +1,422 @@ /*************************************************************************** * 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 "logger.hpp" #include "bin/projectitemmodel.h" #include "timeline2/model/timelinefunctions.hpp" +#include "timeline2/model/timelineitemmodel.hpp" #include "timeline2/model/timelinemodel.hpp" #include #include #include #include #include #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 thread_local bool Logger::is_executing = false; std::mutex Logger::mut; std::vector Logger::operations; std::vector Logger::invoks; std::unordered_map> Logger::constr; std::unordered_map Logger::translation_table; std::unordered_map Logger::back_translation_table; int Logger::dump_count = 0; thread_local size_t Logger::result_awaiting = INT_MAX; void Logger::init() { std::string cur_ind = "a"; auto incr_ind = [&](auto &&self, size_t i = 0) { if (i >= cur_ind.size()) { cur_ind += "a"; return; } if (cur_ind[i] == 'z') { cur_ind[i] = 'A'; } else if (cur_ind[i] == 'Z') { cur_ind[i] = 'a'; self(self, i + 1); } else { cur_ind[i]++; } }; - for (const auto &o : {"TimelineModel", "TrackModel", "test_producer", "test_producer_sound"}) { + for (const auto &o : {"TimelineModel", "TrackModel", "test_producer", "test_producer_sound", "ClipModel"}) { translation_table[std::string("constr_") + o] = cur_ind; incr_ind(incr_ind); } for (const auto &m : rttr::type::get().get_methods()) { translation_table[m.get_name().to_string()] = cur_ind; incr_ind(incr_ind); } for (const auto &m : rttr::type::get().get_methods()) { translation_table[m.get_name().to_string()] = cur_ind; incr_ind(incr_ind); } for (const auto &i : translation_table) { back_translation_table[i.second] = i.first; } } bool Logger::start_logging() { std::unique_lock lk(mut); if (is_executing) { return false; } is_executing = true; return true; } void Logger::stop_logging() { std::unique_lock lk(mut); is_executing = false; } std::string Logger::get_ptr_name(const rttr::variant &ptr) { if (ptr.can_convert()) { return "timeline_" + std::to_string(get_id_from_ptr(ptr.convert())); } else if (ptr.can_convert()) { return "binModel"; + } else if (ptr.can_convert()) { + return "timeline_" + std::to_string(get_id_from_ptr(static_cast(ptr.convert()))); } else { std::cout << "Error: unhandled ptr type " << ptr.get_type().get_name().to_string() << std::endl; } return "unknown"; } void Logger::log_res(rttr::variant result) { std::unique_lock lk(mut); Q_ASSERT(result_awaiting < invoks.size()); invoks[result_awaiting].res = std::move(result); } void Logger::log_create_producer(const std::string &type, std::vector args) { std::unique_lock lk(mut); for (auto &a : args) { // this will rewove shared/weak/unique ptrs if (a.get_type().is_wrapper()) { a = a.extract_wrapped_value(); } const std::string class_name = a.get_type().get_name().to_string(); } constr[type].push_back({type, std::move(args)}); operations.emplace_back(ConstrId{type, constr[type].size() - 1}); } 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 &"); } std::string quoted(const std::string &input) { #if __cpp_lib_quoted_string_io std::stringstream ss; ss << std::quoted(input); return ss.str(); #else // very incomplete implem return "\"" + input + "\""; #endif } } // namespace void Logger::print_trace() { dump_count++; auto process_args = [&](const std::vector &args, const std::unordered_set &refs = {}) { std::stringstream ss; bool deb = true; size_t i = 0; for (const auto &a : args) { if (deb) { deb = false; i = 0; } else { ss << ", "; ++i; } if (refs.count(i) > 0) { ss << "dummy_" << i; } else if (a.get_type() == rttr::type::get()) { ss << a.convert(); + } else if (a.get_type() == rttr::type::get()) { + ss << a.convert(); + } else if (a.get_type() == rttr::type::get()) { + ss << a.convert(); + } else if (a.get_type() == rttr::type::get()) { + ss << a.convert(); } else if (a.get_type() == rttr::type::get()) { ss << (a.convert() ? "true" : "false"); } else if (a.get_type().is_enumeration()) { auto e = a.get_type().get_enumeration(); ss << e.get_name().to_string() << "::" << a.convert(); } else if (a.can_convert()) { ss << quoted(a.convert().toStdString()); } else if (a.can_convert()) { ss << quoted(a.convert()); } else if (a.can_convert>()) { auto set = a.convert>(); ss << "{"; bool beg = true; for (int s : set) { if (beg) beg = false; else ss << ", "; ss << s; } ss << "}"; } else if (a.get_type().is_pointer()) { ss << get_ptr_name(a); } else { std::cout << "Error: unhandled arg type " << a.get_type().get_name().to_string() << std::endl; } } return ss.str(); }; auto process_args_fuzz = [&](const std::vector &args, const std::unordered_set &refs = {}) { std::stringstream ss; bool deb = true; size_t i = 0; for (const auto &a : args) { if (deb) { deb = false; i = 0; } else { ss << " "; ++i; } if (refs.count(i) > 0) { continue; } else if (a.get_type() == rttr::type::get()) { ss << a.convert(); + } else if (a.get_type() == rttr::type::get()) { + ss << a.convert(); + } else if (a.get_type() == rttr::type::get()) { + ss << a.convert(); + } else if (a.get_type() == rttr::type::get()) { + ss << a.convert(); } else if (a.get_type() == rttr::type::get()) { ss << (a.convert() ? "1" : "0"); } else if (a.get_type().is_enumeration()) { ss << a.convert(); } else if (a.can_convert()) { std::string out = a.convert().toStdString(); if (out.empty()) { out = "$$"; } ss << out; } else if (a.can_convert()) { std::string out = a.convert(); if (out.empty()) { out = "$$"; } ss << out; } else if (a.can_convert>()) { auto set = a.convert>(); ss << set.size() << " "; bool beg = true; for (int s : set) { if (beg) beg = false; else ss << " "; ss << s; } } else if (a.get_type().is_pointer()) { if (a.can_convert()) { ss << get_id_from_ptr(a.convert()); + } else if (a.can_convert()) { + ss << get_id_from_ptr(static_cast(a.convert())); } else if (a.can_convert()) { // only one binModel, we skip the parameter since it's unambiguous } else { std::cout << "Error: unhandled ptr type " << a.get_type().get_name().to_string() << std::endl; } } else { std::cout << "Error: unhandled arg type " << a.get_type().get_name().to_string() << std::endl; } } return ss.str(); }; std::ofstream fuzz_file; fuzz_file.open("fuzz_case_" + std::to_string(dump_count) + ".txt"); std::ofstream test_file; test_file.open("test_case_" + std::to_string(dump_count) + ".cpp"); test_file << "TEST_CASE(\"Regression\") {" << std::endl; test_file << "auto binModel = pCore->projectItemModel();" << std::endl; test_file << "binModel->clean();" << std::endl; test_file << "std::shared_ptr undoStack = std::make_shared(nullptr);" << std::endl; test_file << "std::shared_ptr guideModel = std::make_shared(undoStack);" << std::endl; test_file << "TimelineModel::next_id = 0;" << std::endl; test_file << "{" << std::endl; test_file << "Mock pmMock;" << std::endl; test_file << "When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);" << std::endl; test_file << "ProjectManager &mocked = pmMock.get();" << std::endl; test_file << "pCore->m_projectManager = &mocked;" << std::endl; size_t nbrConstructedTimelines = 0; auto check_consistancy = [&]() { for (size_t i = 0; i < nbrConstructedTimelines; ++i) { test_file << "REQUIRE(timeline_" << i << "->checkConsistency());" << std::endl; } }; for (const auto &o : operations) { if (o.can_convert()) { InvokId id = o.convert(); Invok &invok = invoks[id.id]; std::unordered_set refs; + bool is_static = false; rttr::method m = invok.ptr.get_type().get_method(invok.method); + if (!m.is_valid()) { + is_static = true; + m = rttr::type::get_by_name("TimelineFunctions").get_method(invok.method); + } + if (!m.is_valid()) { + std::cout << "ERROR: unknown method " << invok.method << std::endl; + continue; + } test_file << "{" << std::endl; for (const auto &a : m.get_parameter_infos()) { if (isIthParamARef(m, a.get_index())) { refs.insert(a.get_index()); test_file << a.get_type().get_name().to_string() << " dummy_" << std::to_string(a.get_index()) << ";" << std::endl; } } if (m.get_return_type() != rttr::type::get()) { test_file << m.get_return_type().get_name().to_string() << " res = "; } - test_file << get_ptr_name(invok.ptr) << "->" << invok.method << "(" << process_args(invok.args, refs) << ");" << std::endl; + if (is_static) { + test_file << "TimelineFunctions::" << invok.method << "(" << get_ptr_name(invok.ptr) << ", " << process_args(invok.args, refs) << ");" + << std::endl; + } else { + test_file << get_ptr_name(invok.ptr) << "->" << invok.method << "(" << process_args(invok.args, refs) << ");" << std::endl; + } if (m.get_return_type() != rttr::type::get() && invok.res.is_valid()) { test_file << "REQUIRE( res == " << invok.res.to_string() << ");" << std::endl; } test_file << "}" << std::endl; std::string invok_name = invok.method; if (translation_table.count(invok_name) > 0) { auto args = invok.args; - if (rttr::type::get().get_method(invok_name).is_valid()) { + if (rttr::type::get().get_method(invok_name).is_valid() || + rttr::type::get().get_method(invok_name).is_valid()) { args.insert(args.begin(), invok.ptr); // adding an arg just messed up the references std::unordered_set new_refs; for (const size_t &r : refs) { new_refs.insert(r + 1); } std::swap(refs, new_refs); } fuzz_file << translation_table[invok_name] << " " << process_args_fuzz(args, refs) << std::endl; } else { std::cout << "ERROR: unknown method " << invok_name << std::endl; } } else if (o.can_convert()) { ConstrId id = o.convert(); std::string constr_name = std::string("constr_") + id.type; if (translation_table.count(constr_name) > 0) { fuzz_file << translation_table[constr_name] << " " << process_args_fuzz(constr[id.type][id.id].second) << std::endl; } else { std::cout << "ERROR: unknown constructor " << constr_name << std::endl; } if (id.type == "TimelineModel") { test_file << "TimelineItemModel tim_" << id.id << "(®_profile, undoStack);" << std::endl; test_file << "Mock timMock_" << id.id << "(tim_" << id.id << ");" << std::endl; test_file << "auto timeline_" << id.id << " = std::shared_ptr(&timMock_" << id.id << ".get(), [](...) {});" << std::endl; test_file << "TimelineItemModel::finishConstruct(timeline_" << id.id << ", guideModel);" << std::endl; test_file << "Fake(Method(timMock_" << id.id << ", adjustAssetRange));" << std::endl; nbrConstructedTimelines++; } else if (id.type == "TrackModel") { std::string params = process_args(constr[id.type][id.id].second); test_file << "TrackModel::construct(" << params << ");" << std::endl; + } else if (id.type == "ClipModel") { + std::string params = process_args(constr[id.type][id.id].second); + test_file << "ClipModel::construct(" << params << ");" << std::endl; } else if (id.type == "test_producer") { std::string params = process_args(constr[id.type][id.id].second); test_file << "createProducer(reg_profile, " << params << ");" << std::endl; } else if (id.type == "test_producer_sound") { std::string params = process_args(constr[id.type][id.id].second); test_file << "createProducerWithSound(reg_profile, " << params << ");" << std::endl; } else { std::cout << "Error: unknown constructor " << id.type << std::endl; } } else { std::cout << "Error: unknown operation" << std::endl; } check_consistancy(); test_file << "undoStack->undo();" << std::endl; check_consistancy(); test_file << "undoStack->redo();" << std::endl; check_consistancy(); } test_file << "}" << std::endl; test_file << "pCore->m_projectManager = nullptr;" << std::endl; test_file << "}" << std::endl; } void Logger::clear() { is_executing = false; invoks.clear(); operations.clear(); constr.clear(); } LogGuard::LogGuard() { m_hasGuard = Logger::start_logging(); } LogGuard::~LogGuard() { if (m_hasGuard) { Logger::stop_logging(); } } bool LogGuard::hasGuard() const { return m_hasGuard; } diff --git a/src/timeline2/model/clipmodel.cpp b/src/timeline2/model/clipmodel.cpp index b175a02b4..fa85b10a7 100644 --- a/src/timeline2/model/clipmodel.cpp +++ b/src/timeline2/model/clipmodel.cpp @@ -1,696 +1,698 @@ /*************************************************************************** * 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 "clipmodel.hpp" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "core.h" #include "effects/effectstack/model/effectstackmodel.hpp" +#include "logger.hpp" #include "macros.hpp" #include "timelinemodel.hpp" #include "trackmodel.hpp" #include #include #include #include ClipModel::ClipModel(const std::shared_ptr &parent, std::shared_ptr prod, const QString &binClipId, int id, PlaylistState::ClipState state, double speed) : MoveableItem(parent, id) , m_producer(std::move(prod)) , m_effectStack(EffectStackModel::construct(m_producer, {ObjectType::TimelineClip, m_id}, parent->m_undoStack)) , m_binClipId(binClipId) , forceThumbReload(false) , m_currentState(state) , m_speed(speed) , m_fakeTrack(-1) { m_producer->set("kdenlive:id", binClipId.toUtf8().constData()); m_producer->set("_kdenlive_cid", m_id); std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); m_canBeVideo = binClip->hasVideo(); m_canBeAudio = binClip->hasAudio(); m_clipType = binClip->clipType(); if (binClip) { m_endlessResize = !binClip->hasLimitedDuration(); } else { m_endlessResize = false; } QObject::connect(m_effectStack.get(), &EffectStackModel::dataChanged, [&](const QModelIndex &, const QModelIndex &, QVector roles) { qDebug() << "// GOT CLIP STACK DATA CHANGE: " << roles; if (m_currentTrackId != -1) { if (auto ptr = m_parent.lock()) { QModelIndex ix = ptr->makeClipIndexFromID(m_id); qDebug() << "// GOT CLIP STACK DATA CHANGE DONE: " << ix << " = " << roles; ptr->dataChanged(ix, ix, roles); } } }); } int ClipModel::construct(const std::shared_ptr &parent, const QString &binClipId, int id, PlaylistState::ClipState state, double speed) { id = (id == -1 ? TimelineModel::getNextId() : id); std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(binClipId); // We refine the state according to what the clip can actually produce std::pair videoAudio = stateToBool(state); videoAudio.first = videoAudio.first && binClip->hasVideo(); videoAudio.second = videoAudio.second && binClip->hasAudio(); state = stateFromBool(videoAudio); std::shared_ptr cutProducer = binClip->getTimelineProducer(-1, id, state, speed); std::shared_ptr clip(new ClipModel(parent, cutProducer, binClipId, id, state, speed)); + TRACE_CONSTR(clip.get(), parent, binClipId, id, state, speed); clip->setClipState_lambda(state)(); parent->registerClip(clip); return id; } int ClipModel::construct(const std::shared_ptr &parent, const QString &binClipId, const std::shared_ptr &producer, PlaylistState::ClipState state) { // we hand the producer to the bin clip, and in return we get a cut to a good master producer // We might not be able to use directly the producer that we receive as an argument, because it cannot share the same master producer with any other // clipModel (due to a mlt limitation, see ProjectClip doc) int id = TimelineModel::getNextId(); std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(binClipId); // We refine the state according to what the clip can actually produce std::pair videoAudio = stateToBool(state); videoAudio.first = videoAudio.first && binClip->hasVideo(); videoAudio.second = videoAudio.second && binClip->hasAudio(); state = stateFromBool(videoAudio); double speed = 1.0; if (QString::fromUtf8(producer->parent().get("mlt_service")) == QLatin1String("timewarp")) { speed = producer->parent().get_double("warp_speed"); } auto result = binClip->giveMasterAndGetTimelineProducer(id, producer, state); std::shared_ptr clip(new ClipModel(parent, result.first, binClipId, id, state, speed)); clip->setClipState_lambda(state)(); clip->m_effectStack->importEffects(producer, state, result.second); parent->registerClip(clip); return id; } void ClipModel::registerClipToBin(std::shared_ptr service, bool registerProducer) { std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); if (!binClip) { qDebug() << "Error : Bin clip for id: " << m_binClipId << " NOT AVAILABLE!!!"; } qDebug() << "REGISTRATION " << m_id << "ptr count" << m_parent.use_count(); binClip->registerService(m_parent, m_id, std::move(service), registerProducer); } void ClipModel::deregisterClipToBin() { std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); binClip->deregisterTimelineClip(m_id); } ClipModel::~ClipModel() = default; bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo) { QWriteLocker locker(&m_lock); // qDebug() << "RESIZE CLIP" << m_id << "target size=" << size << "right=" << right << "endless=" << m_endlessResize << "length" << // m_producer->get_length(); if (!m_endlessResize && (size <= 0 || size > m_producer->get_length())) { return false; } int delta = getPlaytime() - size; if (delta == 0) { return true; } int in = m_producer->get_in(); int out = m_producer->get_out(); int old_in = in, old_out = out; // check if there is enough space on the chosen side if (!right && in + delta < 0 && !m_endlessResize) { return false; } if (!m_endlessResize && right && (out - delta >= m_producer->get_length())) { return false; } if (right) { out -= delta; } else { in += delta; } // qDebug() << "Resize facts delta =" << delta << "old in" << old_in << "old_out" << old_out << "in" << in << "out" << out; std::function track_operation = []() { return true; }; std::function track_reverse = []() { return true; }; int outPoint = out; int inPoint = in; int offset = 0; if (m_endlessResize) { offset = inPoint; outPoint = out - in; inPoint = 0; } if (m_currentTrackId != -1) { if (auto ptr = m_parent.lock()) { track_operation = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, inPoint, outPoint, right); } else { qDebug() << "Error : Moving clip failed because parent timeline is not available anymore"; Q_ASSERT(false); } } else { // Ensure producer is long enough if (m_endlessResize && outPoint > m_producer->parent().get_length()) { m_producer->set("length", outPoint + 1); } } Fun operation = [this, inPoint, outPoint, track_operation]() { if (track_operation()) { m_producer->set_in_and_out(inPoint, outPoint); return true; } return false; }; if (operation()) { // Now, we are in the state in which the timeline should be when we try to revert current action. So we can build the reverse action from here if (m_currentTrackId != -1) { QVector roles{TimelineModel::DurationRole}; if (!right) { roles.push_back(TimelineModel::StartRole); roles.push_back(TimelineModel::InPointRole); } else { roles.push_back(TimelineModel::OutPointRole); } if (auto ptr = m_parent.lock()) { QModelIndex ix = ptr->makeClipIndexFromID(m_id); // TODO: integrate in undo ptr->dataChanged(ix, ix, roles); track_reverse = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, old_in, old_out, right); } } Fun reverse = [this, old_in, old_out, track_reverse]() { if (track_reverse()) { m_producer->set_in_and_out(old_in, old_out); return true; } return false; }; qDebug() << "----------\n-----------\n// ADJUSTING EFFECT LENGTH, LOGUNDO " << logUndo << ", " << old_in << "/" << inPoint << ", " << m_producer->get_playtime(); if (logUndo) { adjustEffectLength(right, old_in, inPoint, old_out - old_in, m_producer->get_playtime(), offset, reverse, operation, logUndo); } UPDATE_UNDO_REDO(operation, reverse, undo, redo); return true; } return false; } const QString ClipModel::getProperty(const QString &name) const { READ_LOCK(); if (service()->parent().is_valid()) { return QString::fromUtf8(service()->parent().get(name.toUtf8().constData())); } return QString::fromUtf8(service()->get(name.toUtf8().constData())); } int ClipModel::getIntProperty(const QString &name) const { READ_LOCK(); if (service()->parent().is_valid()) { return service()->parent().get_int(name.toUtf8().constData()); } return service()->get_int(name.toUtf8().constData()); } QSize ClipModel::getFrameSize() const { READ_LOCK(); if (service()->parent().is_valid()) { return QSize(service()->parent().get_int("meta.media.width"), service()->parent().get_int("meta.media.height")); } return {service()->get_int("meta.media.width"), service()->get_int("meta.media.height")}; } double ClipModel::getDoubleProperty(const QString &name) const { READ_LOCK(); if (service()->parent().is_valid()) { return service()->parent().get_double(name.toUtf8().constData()); } return service()->get_double(name.toUtf8().constData()); } Mlt::Producer *ClipModel::service() const { READ_LOCK(); return m_producer.get(); } std::shared_ptr ClipModel::getProducer() { READ_LOCK(); return m_producer; } int ClipModel::getPlaytime() const { READ_LOCK(); return m_producer->get_playtime(); } void ClipModel::setTimelineEffectsEnabled(bool enabled) { QWriteLocker locker(&m_lock); m_effectStack->setEffectStackEnabled(enabled); } bool ClipModel::addEffect(const QString &effectId) { QWriteLocker locker(&m_lock); if (EffectsRepository::get()->getType(effectId) == EffectType::Audio) { if (m_currentState == PlaylistState::VideoOnly) { return false; } } else if (m_currentState == PlaylistState::AudioOnly) { return false; } m_effectStack->appendEffect(effectId); return true; } bool ClipModel::copyEffect(const std::shared_ptr &stackModel, int rowId) { QWriteLocker locker(&m_lock); m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId), m_currentState); return true; } bool ClipModel::importEffects(std::shared_ptr stackModel) { QWriteLocker locker(&m_lock); m_effectStack->importEffects(std::move(stackModel), m_currentState); return true; } bool ClipModel::importEffects(std::weak_ptr service) { QWriteLocker locker(&m_lock); m_effectStack->importEffects(std::move(service), m_currentState); return true; } bool ClipModel::removeFade(bool fromStart) { QWriteLocker locker(&m_lock); m_effectStack->removeFade(fromStart); return true; } bool ClipModel::adjustEffectLength(bool adjustFromEnd, int oldIn, int newIn, int oldDuration, int duration, int offset, Fun &undo, Fun &redo, bool logUndo) { QWriteLocker locker(&m_lock); return m_effectStack->adjustStackLength(adjustFromEnd, oldIn, oldDuration, newIn, duration, offset, undo, redo, logUndo); } bool ClipModel::adjustEffectLength(const QString &effectName, int duration, int originalDuration, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); qDebug() << ".... ADJUSTING FADE LENGTH: " << duration << " / " << effectName; Fun operation = [this, duration, effectName, originalDuration]() { return m_effectStack->adjustFadeLength(duration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"), audioEnabled(), !isAudioOnly(), originalDuration > 0); }; if (operation() && originalDuration > 0) { Fun reverse = [this, originalDuration, effectName]() { return m_effectStack->adjustFadeLength(originalDuration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"), audioEnabled(), !isAudioOnly(), true); }; UPDATE_UNDO_REDO(operation, reverse, undo, redo); } return true; } bool ClipModel::audioEnabled() const { READ_LOCK(); return stateToBool(m_currentState).second; } bool ClipModel::isAudioOnly() const { READ_LOCK(); return m_currentState == PlaylistState::AudioOnly; } void ClipModel::refreshProducerFromBin(PlaylistState::ClipState state, double speed) { // We require that the producer is not in the track when we refresh the producer, because otherwise the modification will not be propagated. Remove the clip // first, refresh, and then replant. QWriteLocker locker(&m_lock); int in = getIn(); int out = getOut(); qDebug() << "refresh " << speed << m_speed << in << out; if (!qFuzzyCompare(speed, m_speed) && !qFuzzyCompare(speed, 0.)) { in = in * std::abs(m_speed / speed); out = in + getPlaytime() - 1; // prevent going out of the clip's range out = std::min(out, int(double(m_producer->get_length()) * std::abs(m_speed / speed)) - 1); m_speed = speed; qDebug() << "changing speed" << in << out << m_speed; } std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); std::shared_ptr binProducer = binClip->getTimelineProducer(m_currentTrackId, m_id, state, m_speed); m_producer = std::move(binProducer); m_producer->set_in_and_out(in, out); // replant effect stack in updated service m_effectStack->resetService(m_producer); m_producer->set("kdenlive:id", binClip->clipId().toUtf8().constData()); m_producer->set("_kdenlive_cid", m_id); m_endlessResize = !binClip->hasLimitedDuration(); } void ClipModel::refreshProducerFromBin() { refreshProducerFromBin(m_currentState); } bool ClipModel::useTimewarpProducer(double speed, Fun &undo, Fun &redo) { if (m_endlessResize) { // no timewarp for endless producers return false; } if (qFuzzyCompare(speed, m_speed)) { // nothing to do return true; } std::function local_undo = []() { return true; }; std::function local_redo = []() { return true; }; double previousSpeed = getSpeed(); int oldDuration = getPlaytime(); int newDuration = int(double(oldDuration) * std::abs(previousSpeed / speed)); int oldOut = getOut(); int oldIn = getIn(); auto operation = useTimewarpProducer_lambda(speed); auto reverse = useTimewarpProducer_lambda(previousSpeed); if (oldOut >= newDuration) { // in that case, we are going to shrink the clip when changing the producer. We must undo that when reloading the old producer reverse = [reverse, oldIn, oldOut, this]() { bool res = reverse(); if (res) { setInOut(oldIn, oldOut); } return res; }; } if (operation()) { UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo); bool res = requestResize(newDuration, true, local_undo, local_redo, true); if (!res) { local_undo(); return false; } UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } qDebug() << "tw: operation fail"; return false; } Fun ClipModel::useTimewarpProducer_lambda(double speed) { QWriteLocker locker(&m_lock); return [speed, this]() { qDebug() << "timeWarp producer" << speed; refreshProducerFromBin(m_currentState, speed); if (auto ptr = m_parent.lock()) { QModelIndex ix = ptr->makeClipIndexFromID(m_id); ptr->notifyChange(ix, ix, TimelineModel::SpeedRole); } return true; }; } QVariant ClipModel::getAudioWaveform() { READ_LOCK(); std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); if (binClip) { return QVariant::fromValue(binClip->audioFrameCache); } return QVariant(); } const QString &ClipModel::binId() const { return m_binClipId; } std::shared_ptr ClipModel::getMarkerModel() const { READ_LOCK(); return pCore->projectItemModel()->getClipByBinID(m_binClipId)->getMarkerModel(); } int ClipModel::audioChannels() const { READ_LOCK(); return pCore->projectItemModel()->getClipByBinID(m_binClipId)->audioChannels(); } int ClipModel::fadeIn() const { return m_effectStack->getFadePosition(true); } int ClipModel::fadeOut() const { return m_effectStack->getFadePosition(false); } double ClipModel::getSpeed() const { return m_speed; } KeyframeModel *ClipModel::getKeyframeModel() { return m_effectStack->getEffectKeyframeModel(); } bool ClipModel::showKeyframes() const { READ_LOCK(); return !service()->get_int("kdenlive:hide_keyframes"); } void ClipModel::setShowKeyframes(bool show) { QWriteLocker locker(&m_lock); service()->set("kdenlive:hide_keyframes", (int)!show); } void ClipModel::setCurrentTrackId(int tid, bool finalMove) { if (tid == m_currentTrackId) { return; } MoveableItem::setCurrentTrackId(tid, finalMove); if (finalMove && tid != -1) { refreshProducerFromBin(m_currentState); } } Fun ClipModel::setClipState_lambda(PlaylistState::ClipState state) { QWriteLocker locker(&m_lock); return [this, state]() { if (auto ptr = m_parent.lock()) { switch (state) { case PlaylistState::Disabled: m_producer->set("set.test_audio", 1); m_producer->set("set.test_image", 1); break; case PlaylistState::VideoOnly: m_producer->set("set.test_image", 0); break; case PlaylistState::AudioOnly: m_producer->set("set.test_audio", 0); break; default: // error break; } m_currentState = state; if (m_currentTrackId != -1 && ptr->isClip(m_id)) { // if this is false, the clip is being created. Don't update model in that case QModelIndex ix = ptr->makeClipIndexFromID(m_id); ptr->dataChanged(ix, ix, {TimelineModel::StatusRole}); } return true; } return false; }; } bool ClipModel::setClipState(PlaylistState::ClipState state, Fun &undo, Fun &redo) { if (state == PlaylistState::VideoOnly && !canBeVideo()) { return false; } if (state == PlaylistState::AudioOnly && !canBeAudio()) { return false; } if (state == m_currentState) { return true; } auto old_state = m_currentState; auto operation = setClipState_lambda(state); if (operation()) { auto reverse = setClipState_lambda(old_state); UPDATE_UNDO_REDO(operation, reverse, undo, redo); return true; } return false; } PlaylistState::ClipState ClipModel::clipState() const { READ_LOCK(); return m_currentState; } ClipType::ProducerType ClipModel::clipType() const { READ_LOCK(); return m_clipType; } void ClipModel::passTimelineProperties(const std::shared_ptr &other) { READ_LOCK(); Mlt::Properties source(m_producer->get_properties()); Mlt::Properties dest(other->service()->get_properties()); dest.pass_list(source, "kdenlive:hide_keyframes,kdenlive:activeeffect"); } bool ClipModel::canBeVideo() const { return m_canBeVideo; } bool ClipModel::canBeAudio() const { return m_canBeAudio; } const QString ClipModel::effectNames() const { READ_LOCK(); return m_effectStack->effectNames(); } int ClipModel::getFakeTrackId() const { return m_fakeTrack; } void ClipModel::setFakeTrackId(int fid) { m_fakeTrack = fid; } int ClipModel::getFakePosition() const { return m_fakePosition; } void ClipModel::setFakePosition(int fid) { m_fakePosition = fid; } QDomElement ClipModel::toXml(QDomDocument &document) { QDomElement container = document.createElement(QStringLiteral("clip")); container.setAttribute(QStringLiteral("binid"), m_binClipId); container.setAttribute(QStringLiteral("id"), m_id); container.setAttribute(QStringLiteral("in"), getIn()); container.setAttribute(QStringLiteral("out"), getOut()); container.setAttribute(QStringLiteral("position"), getPosition()); container.setAttribute(QStringLiteral("state"), (int)m_currentState); if (auto ptr = m_parent.lock()) { int trackId = ptr->getTrackPosition(m_currentTrackId); container.setAttribute(QStringLiteral("track"), trackId); if (ptr->isAudioTrack(getCurrentTrackId())) { container.setAttribute(QStringLiteral("audioTrack"), 1); int mirrorId = ptr->getMirrorVideoTrackId(m_currentTrackId); if (mirrorId > -1) { mirrorId = ptr->getTrackPosition(mirrorId); } container.setAttribute(QStringLiteral("mirrorTrack"), mirrorId); } } container.setAttribute(QStringLiteral("speed"), m_speed); container.appendChild(m_effectStack->toXml(document)); return container; } bool ClipModel::checkConsistency() { if (!m_effectStack->checkConsistency()) { qDebug() << "Consistency check failed for effecstack"; return false; } std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); auto instances = binClip->timelineInstances(); bool found = false; for (const auto &i : instances) { if (i == m_id) { found = true; break; } } if (!found) { qDebug() << "ERROR: binClip doesn't acknowledge timeline clip existence"; return false; } if (m_currentState == PlaylistState::VideoOnly && !m_canBeVideo) { qDebug() << "ERROR: clip is in video state but doesn't have video"; return false; } if (m_currentState == PlaylistState::AudioOnly && !m_canBeAudio) { qDebug() << "ERROR: clip is in video state but doesn't have video"; return false; } // TODO: check speed return true; } int ClipModel::getSubPlaylistIndex() const { return m_subPlaylistIndex; } void ClipModel::setSubPlaylistIndex(int index) { m_subPlaylistIndex = index; } diff --git a/src/timeline2/model/timelineitemmodel.cpp b/src/timeline2/model/timelineitemmodel.cpp index c5db02c90..a1d95dae8 100644 --- a/src/timeline2/model/timelineitemmodel.cpp +++ b/src/timeline2/model/timelineitemmodel.cpp @@ -1,600 +1,614 @@ /*************************************************************************** * 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 "timelineitemmodel.hpp" #include "assets/keyframes/model/keyframemodel.hpp" #include "bin/model/markerlistmodel.hpp" #include "clipmodel.hpp" #include "compositionmodel.hpp" #include "core.h" #include "doc/docundostack.hpp" #include "groupsmodel.hpp" #include "kdenlivesettings.h" #include "macros.hpp" #include "trackmodel.hpp" #include "transitions/transitionsrepository.hpp" #include #include #include #include #include #include +#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 +RTTR_REGISTRATION +{ + using namespace rttr; + registration::class_("TimelineItemModel"); +} + TimelineItemModel::TimelineItemModel(Mlt::Profile *profile, std::weak_ptr undo_stack) : TimelineModel(profile, std::move(undo_stack)) { } void TimelineItemModel::finishConstruct(const std::shared_ptr &ptr, const std::shared_ptr &guideModel) { ptr->weak_this_ = ptr; ptr->m_groups = std::make_unique(ptr); guideModel->registerSnapModel(ptr->m_snaps); } std::shared_ptr TimelineItemModel::construct(Mlt::Profile *profile, std::shared_ptr guideModel, std::weak_ptr undo_stack) { std::shared_ptr ptr(new TimelineItemModel(profile, std::move(undo_stack))); finishConstruct(ptr, std::move(guideModel)); return ptr; } TimelineItemModel::~TimelineItemModel() = default; QModelIndex TimelineItemModel::index(int row, int column, const QModelIndex &parent) const { READ_LOCK(); QModelIndex result; if (parent.isValid()) { auto trackId = int(parent.internalId()); Q_ASSERT(isTrack(trackId)); int clipId = getTrackById_const(trackId)->getClipByRow(row); if (clipId != -1) { result = createIndex(row, 0, quintptr(clipId)); } else if (row < getTrackClipsCount(trackId) + getTrackCompositionsCount(trackId)) { int compoId = getTrackById_const(trackId)->getCompositionByRow(row); if (compoId != -1) { result = createIndex(row, 0, quintptr(compoId)); } } else { // Invalid index requested Q_ASSERT(false); } } else if (row < getTracksCount() && row >= 0) { // Get sort order // row = getTracksCount() - 1 - row; auto it = m_allTracks.cbegin(); std::advance(it, row); int trackId = (*it)->getId(); result = createIndex(row, column, quintptr(trackId)); } return result; } /*QModelIndex TimelineItemModel::makeIndex(int trackIndex, int clipIndex) const { return index(clipIndex, 0, index(trackIndex)); }*/ QModelIndex TimelineItemModel::makeClipIndexFromID(int clipId) const { Q_ASSERT(m_allClips.count(clipId) > 0); int trackId = m_allClips.at(clipId)->getCurrentTrackId(); if (trackId == -1) { // Clip is not inserted in a track qDebug() << "/// WARNING; INVALID CLIP INDEX REQUESTED\n________________"; return {}; } int row = getTrackById_const(trackId)->getRowfromClip(clipId); return index(row, 0, makeTrackIndexFromID(trackId)); } QModelIndex TimelineItemModel::makeCompositionIndexFromID(int compoId) const { Q_ASSERT(m_allCompositions.count(compoId) > 0); int trackId = m_allCompositions.at(compoId)->getCurrentTrackId(); return index(getTrackById_const(trackId)->getRowfromComposition(compoId), 0, makeTrackIndexFromID(trackId)); } QModelIndex TimelineItemModel::makeTrackIndexFromID(int trackId) const { // we retrieve iterator Q_ASSERT(m_iteratorTable.count(trackId) > 0); auto it = m_iteratorTable.at(trackId); int ind = (int)std::distance(m_allTracks.begin(), it); // Get sort order // ind = getTracksCount() - 1 - ind; return index(ind); } QModelIndex TimelineItemModel::parent(const QModelIndex &index) const { READ_LOCK(); // qDebug() << "TimelineItemModel::parent"<< index; if (index == QModelIndex()) { return index; } const int id = static_cast(index.internalId()); if (!index.isValid() || isTrack(id)) { return QModelIndex(); } if (isClip(id)) { const int trackId = getClipTrackId(id); return makeTrackIndexFromID(trackId); } if (isComposition(id)) { const int trackId = getCompositionTrackId(id); return makeTrackIndexFromID(trackId); } return {}; } int TimelineItemModel::rowCount(const QModelIndex &parent) const { READ_LOCK(); if (parent.isValid()) { const int id = (int)parent.internalId(); if (!isTrack(id)) { // clips don't have children // if it is not a track, it is something invalid return 0; } return getTrackClipsCount(id) + getTrackCompositionsCount(id); } return getTracksCount(); } int TimelineItemModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } QHash TimelineItemModel::roleNames() const { QHash roles; roles[NameRole] = "name"; roles[ResourceRole] = "resource"; roles[ServiceRole] = "mlt_service"; roles[BinIdRole] = "binId"; roles[TrackIdRole] = "trackId"; roles[FakeTrackIdRole] = "fakeTrackId"; roles[FakePositionRole] = "fakePosition"; roles[StartRole] = "start"; roles[DurationRole] = "duration"; roles[MarkersRole] = "markers"; roles[KeyframesRole] = "keyframeModel"; roles[ShowKeyframesRole] = "showKeyframes"; roles[StatusRole] = "clipStatus"; roles[TypeRole] = "clipType"; roles[InPointRole] = "in"; roles[OutPointRole] = "out"; roles[FramerateRole] = "fps"; roles[GroupedRole] = "grouped"; roles[IsDisabledRole] = "disabled"; roles[IsAudioRole] = "audio"; roles[AudioLevelsRole] = "audioLevels"; roles[AudioChannelsRole] = "audioChannels"; roles[IsCompositeRole] = "composite"; roles[IsLockedRole] = "locked"; roles[FadeInRole] = "fadeIn"; roles[FadeOutRole] = "fadeOut"; roles[FileHashRole] = "hash"; roles[SpeedRole] = "speed"; roles[HeightRole] = "trackHeight"; roles[TrackTagRole] = "trackTag"; roles[ItemIdRole] = "item"; roles[ItemATrack] = "a_track"; roles[HasAudio] = "hasAudio"; roles[CanBeAudioRole] = "canBeAudio"; roles[CanBeVideoRole] = "canBeVideo"; roles[ReloadThumbRole] = "reloadThumb"; roles[ThumbsFormatRole] = "thumbsFormat"; roles[AudioRecordRole] = "audioRecord"; roles[TrackActiveRole] = "trackActive"; roles[EffectNamesRole] = "effectNames"; roles[EffectsEnabledRole] = "isStackEnabled"; roles[GrabbedRole] = "isGrabbed"; return roles; } QVariant TimelineItemModel::data(const QModelIndex &index, int role) const { READ_LOCK(); if (!m_tractor || !index.isValid()) { // qDebug() << "DATA abort. Index validity="< clip = m_allClips.at(id); // Get data for a clip switch (role) { // TODO case NameRole: case Qt::DisplayRole: { QString result = clip->getProperty("kdenlive:clipname"); if (result.isEmpty()) { result = clip->getProperty("kdenlive:originalurl"); if (result.isEmpty()) { result = clip->getProperty("resource"); } if (!result.isEmpty()) { result = QFileInfo(result).fileName(); } else { result = clip->getProperty("mlt_service"); } } return result; } case ResourceRole: { QString result = clip->getProperty("resource"); if (result == QLatin1String("")) { result = clip->getProperty("mlt_service"); } return result; } case FakeTrackIdRole: return clip->getFakeTrackId(); case FakePositionRole: return clip->getFakePosition(); case BinIdRole: return clip->binId(); case TrackIdRole: return clip->getCurrentTrackId(); case ServiceRole: return clip->getProperty("mlt_service"); break; case AudioLevelsRole: // Dumb property to trigger audio thumbs reload return true; case AudioChannelsRole: return clip->audioChannels(); case HasAudio: return clip->audioEnabled(); case IsAudioRole: return clip->isAudioOnly(); case CanBeAudioRole: return clip->canBeAudio(); case CanBeVideoRole: return clip->canBeVideo(); case MarkersRole: { return QVariant::fromValue(clip->getMarkerModel().get()); } case KeyframesRole: { return QVariant::fromValue(clip->getKeyframeModel()); } case StatusRole: return QVariant::fromValue(clip->clipState()); case TypeRole: return QVariant::fromValue(clip->clipType()); case StartRole: return clip->getPosition(); case DurationRole: return clip->getPlaytime(); case GroupedRole: return m_groups->isInGroup(id); case EffectNamesRole: return clip->effectNames(); case InPointRole: return clip->getIn(); case OutPointRole: return clip->getOut(); case ShowKeyframesRole: return clip->showKeyframes(); case FadeInRole: return clip->fadeIn(); case FadeOutRole: return clip->fadeOut(); case ReloadThumbRole: return clip->forceThumbReload; case SpeedRole: return clip->getSpeed(); case GrabbedRole: return clip->isGrabbed(); default: break; } } else if (isTrack(id)) { // qDebug() << "DATA REQUESTED FOR TRACK "<< id; switch (role) { case NameRole: case Qt::DisplayRole: { return getTrackById_const(id)->getProperty("kdenlive:track_name").toString(); } case TypeRole: return QVariant::fromValue(ClipType::ProducerType::Track); case DurationRole: // qDebug() << "DATA yielding duration" << m_tractor->get_playtime(); return getTrackById_const(id)->trackDuration(); case IsDisabledRole: // qDebug() << "DATA yielding mute" << 0; return getTrackById_const(id)->isAudioTrack() ? getTrackById_const(id)->isMute() : getTrackById_const(id)->isHidden(); case IsAudioRole: return getTrackById_const(id)->isAudioTrack(); case TrackTagRole: return getTrackTagById(id); case IsLockedRole: return getTrackById_const(id)->getProperty("kdenlive:locked_track").toInt() == 1; case HeightRole: { int collapsed = getTrackById_const(id)->getProperty("kdenlive:collapsed").toInt(); if (collapsed > 0) { return collapsed; } int height = getTrackById_const(id)->getProperty("kdenlive:trackheight").toInt(); // qDebug() << "DATA yielding height" << height; return (height > 0 ? height : 60); } case ThumbsFormatRole: return getTrackById_const(id)->getProperty("kdenlive:thumbs_format").toInt(); case IsCompositeRole: { case AudioRecordRole: return getTrackById_const(id)->getProperty("kdenlive:audio_rec").toInt(); } case TrackActiveRole: { return getTrackById_const(id)->isTimelineActive(); } case EffectNamesRole: { return getTrackById_const(id)->effectNames(); } case EffectsEnabledRole: { return getTrackById_const(id)->stackEnabled(); } default: break; } } else if (isComposition(id)) { std::shared_ptr compo = m_allCompositions.at(id); switch (role) { case NameRole: case Qt::DisplayRole: case ResourceRole: case ServiceRole: return compo->displayName(); break; case TypeRole: return QVariant::fromValue(ClipType::ProducerType::Composition); case StartRole: return compo->getPosition(); case TrackIdRole: return compo->getCurrentTrackId(); case DurationRole: return compo->getPlaytime(); case GroupedRole: return m_groups->isInGroup(id); case InPointRole: return 0; case OutPointRole: return 100; case BinIdRole: return 5; case KeyframesRole: { return QVariant::fromValue(compo->getEffectKeyframeModel()); } case ShowKeyframesRole: return compo->showKeyframes(); case ItemATrack: return compo->getForcedTrack(); case MarkersRole: { QVariantList markersList; return markersList; } case GrabbedRole: return compo->isGrabbed(); default: break; } } else { qDebug() << "UNKNOWN DATA requested " << index << roleNames()[role]; } return QVariant(); } void TimelineItemModel::setTrackProperty(int trackId, const QString &name, const QString &value) { std::shared_ptr track = getTrackById(trackId); track->setProperty(name, value); QVector roles; if (name == QLatin1String("kdenlive:track_name")) { roles.push_back(NameRole); } else if (name == QLatin1String("kdenlive:locked_track")) { roles.push_back(IsLockedRole); } else if (name == QLatin1String("hide")) { roles.push_back(IsDisabledRole); if (!track->isAudioTrack()) { pCore->requestMonitorRefresh(); } } else if (name == QLatin1String("kdenlive:timeline_active")) { roles.push_back(TrackActiveRole); } else if (name == QLatin1String("kdenlive:thumbs_format")) { roles.push_back(ThumbsFormatRole); } else if (name == QLatin1String("kdenlive:audio_rec")) { roles.push_back(AudioRecordRole); } if (!roles.isEmpty()) { QModelIndex ix = makeTrackIndexFromID(trackId); emit dataChanged(ix, ix, roles); } } void TimelineItemModel::setTrackStackEnabled(int tid, bool enable) { std::shared_ptr track = getTrackById(tid); track->setEffectStackEnabled(enable); QModelIndex ix = makeTrackIndexFromID(tid); emit dataChanged(ix, ix, {TimelineModel::EffectsEnabledRole}); } void TimelineItemModel::importTrackEffects(int tid, std::weak_ptr service) { std::shared_ptr track = getTrackById(tid); track->importEffects(std::move(service)); } QVariant TimelineItemModel::getTrackProperty(int tid, const QString &name) const { return getTrackById_const(tid)->getProperty(name); } int TimelineItemModel::getFirstVideoTrackIndex() const { int trackId = -1; auto it = m_allTracks.cbegin(); while (it != m_allTracks.cend()) { trackId = (*it)->getId(); if (!(*it)->isAudioTrack()) { break; } ++it; } return trackId; } const QString TimelineItemModel::getTrackFullName(int tid) const { QString tag = getTrackTagById(tid); QString trackName = getTrackById_const(tid)->getProperty(QStringLiteral("kdenlive:track_name")).toString(); return trackName.isEmpty() ? tag : tag + QStringLiteral(" - ") + trackName; } const QString TimelineItemModel::groupsData() { return m_groups->toJson(); } bool TimelineItemModel::loadGroups(const QString &groupsData) { return m_groups->fromJson(groupsData); } void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, bool start, bool duration, bool updateThumb) { QVector roles; if (start) { roles.push_back(TimelineModel::StartRole); if (updateThumb) { roles.push_back(TimelineModel::InPointRole); } } if (duration) { roles.push_back(TimelineModel::DurationRole); if (updateThumb) { roles.push_back(TimelineModel::OutPointRole); } } emit dataChanged(topleft, bottomright, roles); } void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, const QVector &roles) { emit dataChanged(topleft, bottomright, roles); } void TimelineItemModel::buildTrackCompositing(bool rebuild) { auto it = m_allTracks.cbegin(); QScopedPointer field(m_tractor->field()); field->lock(); // Make sure all previous track compositing is removed if (rebuild) { QScopedPointer service(new Mlt::Service(field->get_service())); while ((service != nullptr) && service->is_valid()) { if (service->type() == transition_type) { Mlt::Transition t((mlt_transition)service->get_service()); QString serviceName = t.get("mlt_service"); if (t.get_int("internal_added") == 237) { // remove all compositing transitions field->disconnect_service(t); } } service.reset(service->producer()); } } QString composite = TransitionsRepository::get()->getCompositingTransition(); while (it != m_allTracks.cend()) { int trackId = getTrackMltIndex((*it)->getId()); if (!composite.isEmpty() && !(*it)->isAudioTrack()) { // video track, add composition std::unique_ptr transition = TransitionsRepository::get()->getTransition(composite); transition->set("internal_added", 237); transition->set("always_active", 1); field->plant_transition(*transition, 0, trackId); transition->set_tracks(0, trackId); } else if ((*it)->isAudioTrack()) { // audio mix std::unique_ptr transition = TransitionsRepository::get()->getTransition(QStringLiteral("mix")); transition->set("internal_added", 237); transition->set("always_active", 1); transition->set("sum", 1); field->plant_transition(*transition, 0, trackId); transition->set_tracks(0, trackId); } ++it; } field->unlock(); if (composite.isEmpty()) { pCore->displayMessage(i18n("Could not setup track compositing, check your install"), MessageType::ErrorMessage); } } void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, int role) { emit dataChanged(topleft, bottomright, {role}); } void TimelineItemModel::_beginRemoveRows(const QModelIndex &i, int j, int k) { // qDebug()<<"FORWARDING beginRemoveRows"<