diff --git a/src/assets/keyframes/model/keyframemodel.cpp b/src/assets/keyframes/model/keyframemodel.cpp index a6e7c4a08..8f99b568c 100644 --- a/src/assets/keyframes/model/keyframemodel.cpp +++ b/src/assets/keyframes/model/keyframemodel.cpp @@ -1,599 +1,599 @@ /*************************************************************************** * 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 "doc/docundostack.hpp" #include "core.h" #include "assets/model/assetparametermodel.hpp" #include "macros.hpp" #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:"<getCurrentFps())< 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; double oldValue = m_keyframeList[pos].second; local_undo = updateKeyframe_lambda(pos, oldType, oldValue, notify); local_redo = updateKeyframe_lambda(pos, type, value, notify); } else { qDebug() << "True addittion"; 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(GenTime pos, KeyframeType type, double 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); 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) { qDebug() << "Going to remove keyframe at "<getCurrentFps()); qDebug() << "before"< 0); KeyframeType oldType = m_keyframeList[pos].first; double oldValue = m_keyframeList[pos].second; Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, true); Fun local_redo = deleteKeyframe_lambda(pos, true); qDebug() << "before2"< 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, Fun &undo, Fun &redo) { qDebug() << "starting to move keyframe"<getCurrentFps())<getCurrentFps()); QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(oldPos) > 0); KeyframeType oldType = m_keyframeList[oldPos].first; double oldValue = m_keyframeList[oldPos].second; if (oldPos == pos ) return true; if ( hasKeyframe(pos) ) return false; Fun local_undo = []() { return true; }; Fun local_redo = []() { return true; }; qDebug() << getAnimProperty(); bool res = removeKeyframe(oldPos, local_undo, local_redo); qDebug() << "Move keyframe finished deletion:"< 0); if (oldPos == pos ) return true; Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool res = moveKeyframe(oldPos, pos, undo, redo); if (res && logUndo) { PUSH_UNDO(undo, redo, i18n("Move keyframe")); } return res; } bool KeyframeModel::updateKeyframe(GenTime pos, double value, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(pos) > 0); - KeyframeType oldType = m_keyframeList[pos].first; + KeyframeType type = m_keyframeList[pos].first; double oldValue = m_keyframeList[pos].second; if (qAbs(oldValue - value) < 1e-6) return true; - auto operation = updateKeyframe_lambda(pos, oldType, oldValue, true); - auto reverse = updateKeyframe_lambda(pos, oldType, value, true); + auto operation = updateKeyframe_lambda(pos, type, value, true); + auto reverse = updateKeyframe_lambda(pos, type, oldValue, true); bool res = operation(); if (res) { UPDATE_UNDO_REDO(operation, reverse, undo, redo); } return res; } bool KeyframeModel::updateKeyframe(GenTime pos, double 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); if (res) { PUSH_UNDO(undo, redo, i18n("Update keyframe")); } return res; } Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, double value, bool notify) { QWriteLocker locker(&m_lock); return [this, pos, type, value, notify]() { qDebug() << "udpate lambda"<getCurrentFps())< 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), QVector() << TypeRole << ValueRole); return true; }; } Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, double value, bool notify) { QWriteLocker locker(&m_lock); return [this, notify, pos, type, value]() { qDebug() << "add lambda"<getCurrentFps())<(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"<getCurrentFps())< 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"< KeyframeModel::roleNames() const { QHash roles; roles[PosRole] = "position"; roles[FrameRole] = "frame"; roles[TypeRole] = "type"; roles[ValueRole] = "value"; 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 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()); } 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; }; for (const auto& m : m_keyframeList) { all_pos.push_back(m.first); } 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); if (!res) { bool undone = local_undo(); Q_ASSERT(undone); return false; } } 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; } QString KeyframeModel::getAnimProperty() const { QString prop; bool first = true; for (const auto keyframe : m_keyframeList) { if (first) { first = false; } else { prop += QStringLiteral(";"); } prop += QString::number(keyframe.first.frames(pCore->getCurrentFps())); switch (keyframe.second.first) { case KeyframeType::Linear: prop += QStringLiteral("="); break; case KeyframeType::Discrete: prop += QStringLiteral("|="); break; case KeyframeType::Curve: prop += QStringLiteral("~="); break; } prop += QString::number(keyframe.second.second); } return prop; } 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; } 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; } void KeyframeModel::parseAnimProperty(const QString &prop) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; Mlt::Properties 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_anim("key"); qDebug() << "Found"<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); double value = mlt_prop.anim_get_double("key", frame); addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo); } delete anim; /* std::vector > separators({QStringLiteral("="), QStringLiteral("|="), QStringLiteral("~=")}); QStringList list = prop.split(';', QString::SkipEmptyParts); for (const auto& k : list) { bool found = false; KeyframeType type; QStringList values; for (const auto &sep : separators) { if (k.contains(sep.first)) { found = true; type = sep.second; values = k.split(sep.first); break; } } if (!found || values.size() != 2) { qDebug() << "ERROR while parsing value of keyframe"<getCurrentFps()); return getInterpolatedValue(pos); } double KeyframeModel::getInterpolatedValue(const GenTime &pos) const { int p = pos.frames(pCore->getCurrentFps()); if (m_keyframeList.count(pos) > 0) { return m_keyframeList.at(pos).second; } 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; prop.anim_set("keyframe", prev->second.second, prev->first.frames(pCore->getCurrentFps()), 0, convertToMltType(prev->second.first) ); prop.anim_set("keyframe", next->second.second, next->first.frames(pCore->getCurrentFps()), 0, convertToMltType(next->second.first) ); return prop.anim_get_double("keyframe", p); } void KeyframeModel::sendModification() { if (auto ptr = m_model.lock()) { Q_ASSERT(m_index.isValid()); QString name = ptr->data(m_index, AssetParameterModel::NameRole).toString(); auto type = ptr->data(m_index, AssetParameterModel::TypeRole).value(); QString data; if (type == ParamType::KeyframeParam) { data = getAnimProperty(); ptr->setParameter(name, data); } else { Q_ASSERT(false); //Not implemented, TODO } m_lastData = data; ptr->dataChanged(m_index, m_index); } } void KeyframeModel::refresh() { qDebug() << "REFRESHING KEYFRAME"; if (auto ptr = m_model.lock()) { Q_ASSERT(m_index.isValid()); auto type = ptr->data(m_index, AssetParameterModel::TypeRole).value(); QString data = ptr->data(m_index, AssetParameterModel::ValueRole).toString(); qDebug() << "FOUND DATA KEYFRAME" << data; if (data == m_lastData) { // nothing to do return; } // first, try to convert to double bool ok = false; double value = data.toDouble(&ok); if (ok) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; addKeyframe(GenTime(), KeyframeType::Linear, value, false, undo, redo); qDebug() << "KEYFRAME ADDED"<. * ***************************************************************************/ #include "keyframemodellist.hpp" #include "doc/docundostack.hpp" #include "core.h" #include "macros.hpp" #include "klocalizedstring.h" #include "keyframemodel.hpp" #include "assets/model/assetparametermodel.hpp" #include KeyframeModelList::KeyframeModelList(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack) : m_model(model) , m_undoStack(undo_stack) , m_lock(QReadWriteLock::Recursive) { qDebug() <<"Construct keyframemodellist. Checking model:"<second.get(), &KeyframeModel::modelChanged, this, &KeyframeModelList::modelChanged); } void KeyframeModelList::addParameter(const QModelIndex &index) { std::shared_ptr parameter (new KeyframeModel(m_model, index, m_undoStack)); m_parameters.insert({index, std::move(parameter)}); } bool KeyframeModelList::applyOperation(const std::function, Fun&, Fun&)> &op, const QString &undoString) { QWriteLocker locker(&m_lock); Q_ASSERT(m_parameters.size() > 0); Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool res = true; for (const auto& param : m_parameters) { res = op(param.second, undo, redo); if (!res) { bool undone = undo(); Q_ASSERT(undone); return res; } } if (res && !undoString.isEmpty()) { PUSH_UNDO(undo, redo, undoString); } return res; } bool KeyframeModelList::addKeyframe(GenTime pos, KeyframeType type) { QWriteLocker locker(&m_lock); Q_ASSERT(m_parameters.size() > 0); bool update = (m_parameters.begin()->second->hasKeyframe(pos) > 0); auto op = [pos, type](std::shared_ptr param, Fun &undo, Fun &redo){ double value = param->getInterpolatedValue(pos); return param->addKeyframe(pos, type, value, true, undo, redo); }; return applyOperation(op, update ? i18n("Change keyframe type") : i18n("Add keyframe")); } bool KeyframeModelList::removeKeyframe(GenTime pos) { QWriteLocker locker(&m_lock); Q_ASSERT(m_parameters.size() > 0); auto op = [pos](std::shared_ptr param, Fun &undo, Fun &redo){ return param->removeKeyframe(pos, undo, redo); }; return applyOperation(op, i18n("Delete keyframe")); } bool KeyframeModelList::removeAllKeyframes() { QWriteLocker locker(&m_lock); Q_ASSERT(m_parameters.size() > 0); auto op = [](std::shared_ptr param, Fun &undo, Fun &redo){ return param->removeAllKeyframes(undo, redo); }; return applyOperation(op, i18n("Delete all keyframes")); } bool KeyframeModelList::moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo) { QWriteLocker locker(&m_lock); Q_ASSERT(m_parameters.size() > 0); auto op = [oldPos, pos](std::shared_ptr param, Fun &undo, Fun &redo){ return param->moveKeyframe(oldPos, pos, undo, redo); }; return applyOperation(op, logUndo ? i18n("Move keyframe") : QString()); } -bool KeyframeModelList::updateKeyframe(GenTime pos, double value) +bool KeyframeModelList::updateKeyframe(GenTime pos, double value, const QPersistentModelIndex &index) { QWriteLocker locker(&m_lock); - Q_ASSERT(m_parameters.size() > 0); - auto op = [value, pos](std::shared_ptr param, Fun &undo, Fun &redo){ - return param->updateKeyframe(pos, value, undo, redo); - }; - return applyOperation(op, i18n("Update keyframe")); + Q_ASSERT(m_parameters.count(index) > 0); + Fun undo = []() { return true; }; + Fun redo = []() { return true; }; + bool res = m_parameters.at(index)->updateKeyframe(pos, value, undo, redo); + if (res) { + PUSH_UNDO(undo, redo, i18n("Update keyframe")); + } + return res; } Keyframe KeyframeModelList::getKeyframe(const GenTime &pos, bool *ok) const { READ_LOCK(); Q_ASSERT(m_parameters.size() > 0); return m_parameters.begin()->second->getKeyframe(pos, ok); } Keyframe KeyframeModelList::getNextKeyframe(const GenTime &pos, bool *ok) const { READ_LOCK(); Q_ASSERT(m_parameters.size() > 0); return m_parameters.begin()->second->getNextKeyframe(pos, ok); } Keyframe KeyframeModelList::getPrevKeyframe(const GenTime &pos, bool *ok) const { READ_LOCK(); Q_ASSERT(m_parameters.size() > 0); return m_parameters.begin()->second->getPrevKeyframe(pos, ok); } Keyframe KeyframeModelList::getClosestKeyframe(const GenTime &pos, bool *ok) const { READ_LOCK(); Q_ASSERT(m_parameters.size() > 0); return m_parameters.begin()->second->getClosestKeyframe(pos, ok); } bool KeyframeModelList::hasKeyframe(int frame) const { READ_LOCK(); Q_ASSERT(m_parameters.size() > 0); return m_parameters.begin()->second->hasKeyframe(frame); } void KeyframeModelList::refresh() { + QWriteLocker locker(&m_lock); for (const auto& param : m_parameters) { param.second->refresh(); } } + +double KeyframeModelList::getInterpolatedValue(int pos, const QPersistentModelIndex& index) const +{ + READ_LOCK(); + Q_ASSERT(m_parameters.count(index) > 0); + return m_parameters.at(index)->getInterpolatedValue(pos); +} diff --git a/src/assets/keyframes/model/keyframemodellist.hpp b/src/assets/keyframes/model/keyframemodellist.hpp index 35722465c..1abbc1a8a 100644 --- a/src/assets/keyframes/model/keyframemodellist.hpp +++ b/src/assets/keyframes/model/keyframemodellist.hpp @@ -1,135 +1,141 @@ /*************************************************************************** * 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 KEYFRAMELISTMODELLIST_H #define KEYFRAMELISTMODELLIST_H #include "gentime.h" #include "definitions.h" #include "keyframemodel.hpp" #include "undohelper.hpp" #include #include #include #include #include #include class AssetParameterModel; class DocUndoStack; /* @brief This class is a container for the keyframe models. If an asset has several keyframable parameters, each one has its own keyframeModel, but we regroup all of these in a common class to provide unified access. */ class KeyframeModelList : public QObject { Q_OBJECT public: /* @brief Construct a keyframe list bound to the given asset @param init_value and index correspond to the first parameter */ explicit KeyframeModelList(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack); /* @brief Add a keyframable parameter to be managed by this model */ void addParameter(const QModelIndex &index); /* @brief Adds a keyframe at the given position. If there is already one then we update it. @param pos defines the position of the keyframe, relative to the clip @param type is the type of the keyframe. */ bool addKeyframe(GenTime pos, KeyframeType type); /* @brief Removes the keyframe at the given position. */ bool removeKeyframe(GenTime pos); /* @brief Delete all the keyframes of the model (except first) */ bool removeAllKeyframes(); /* @brief moves a keyframe @param oldPos is the old position of the keyframe @param pos defines the new position of the keyframe, relative to the clip @param logUndo if true, then an undo object is created */ bool moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo); /* @brief updates the value of a keyframe @param old is the position of the keyframe @param value is the new value of the param + @param index is the index of the wanted keyframe */ - bool updateKeyframe(GenTime pos, double value); + bool updateKeyframe(GenTime pos, double value, const QPersistentModelIndex &index); /* @brief Returns a keyframe data at given pos ok is a return parameter, set to true if everything went good */ Keyframe getKeyframe(const GenTime &pos, bool *ok) const; /* @brief Returns the keyframe located after given position. If there is a keyframe at given position it is ignored. @param ok is a return parameter to tell if a keyframe was found. */ Keyframe getNextKeyframe(const GenTime &pos, bool *ok) const; /* @brief Returns the keyframe located before given position. If there is a keyframe at given position it is ignored. @param ok is a return parameter to tell if a keyframe was found. */ Keyframe getPrevKeyframe(const GenTime &pos, bool *ok) const; /* @brief Returns the closest keyframe from given position. @param ok is a return parameter to tell if a keyframe was found. */ Keyframe getClosestKeyframe(const GenTime &pos, bool *ok) const; /* @brief Returns true if a keyframe exists at given pos Notice that add/remove queries are done in real time (gentime), but this request is made in frame */ Q_INVOKABLE bool hasKeyframe(int frame) const; + /* @brief Return the interpolated value of a parameter. + @param pos is the position where we interpolate + @param index is the index of the queried parameter. */ + double getInterpolatedValue(int pos, const QPersistentModelIndex& index) const; + void refresh(); protected: /** @brief Helper function to apply a given operation on all parameters */ bool applyOperation(const std::function, Fun&, Fun&)> &op, const QString &undoString); signals: void modelChanged(); private: std::weak_ptr m_model; std::weak_ptr m_undoStack; std::unordered_map> m_parameters; mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access public: // this is to enable for range loops auto begin() -> decltype(m_parameters.begin()->second->begin()) { return m_parameters.begin()->second->begin(); } auto end() -> decltype(m_parameters.begin()->second->end()) { return m_parameters.begin()->second->end(); } }; #endif diff --git a/src/assets/keyframes/view/keyframeview.cpp b/src/assets/keyframes/view/keyframeview.cpp index cf399135c..14a94bfca 100644 --- a/src/assets/keyframes/view/keyframeview.cpp +++ b/src/assets/keyframes/view/keyframeview.cpp @@ -1,283 +1,284 @@ /*************************************************************************** * Copyright (C) 2011 by Till Theato (root@ttill.de) * * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive (www.kdenlive.org). * * * * Kdenlive 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. * * * * Kdenlive 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 Kdenlive. If not, see . * ***************************************************************************/ #include "keyframeview.hpp" #include "assets/keyframes/model/keyframemodellist.hpp" #include "core.h" #include "kdenlivesettings.h" #include #include #include #include KeyframeView::KeyframeView(std::shared_ptr model, QWidget *parent) : QWidget(parent) , m_model(model) , m_duration(1) , m_position(0) , m_currentKeyframe(-1) , m_currentKeyframeOriginal(-1) , m_hoverKeyframe(-1) , m_scale(1) , m_currentType(KeyframeType::Linear) { setMouseTracking(true); setMinimumSize(QSize(150, 20)); setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum)); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); QPalette p = palette(); KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme())); m_colSelected = palette().highlight().color(); m_colKeyframe = scheme.foreground(KColorScheme::NormalText).color(); m_size = QFontInfo(font()).pixelSize() * 1.8; m_lineHeight = m_size / 2; setMinimumHeight(m_size); setMaximumHeight(m_size); connect(m_model.get(), &KeyframeModelList::modelChanged, this, &KeyframeView::slotModelChanged); } void KeyframeView::slotModelChanged() { emit atKeyframe(m_model->hasKeyframe(m_position)); + emit modified(); update(); } void KeyframeView::slotSetPosition(int pos) { if (pos != m_position) { m_position = pos; emit atKeyframe(m_model->hasKeyframe(pos)); emit seekToPos(pos); update(); } } void KeyframeView::slotAddKeyframe(int pos) { if (pos < 0) { pos = m_position; } m_model->addKeyframe(GenTime(pos, pCore->getCurrentFps()), m_currentType); } void KeyframeView::slotAddRemove() { if (m_model->hasKeyframe(m_position)) { slotRemoveKeyframe(m_position); } else { slotAddKeyframe(m_position); } } void KeyframeView::slotRemoveKeyframe(int pos) { if (pos < 0) { pos = m_position; } m_model->removeKeyframe(GenTime(pos, pCore->getCurrentFps())); } void KeyframeView::setDuration(int dur) { m_duration = dur; } void KeyframeView::slotGoToNext() { if (m_position == m_duration) { return; } bool ok; auto next = m_model->getNextKeyframe(GenTime(m_position, pCore->getCurrentFps()), &ok); if (ok) { slotSetPosition(next.first.frames(pCore->getCurrentFps())); } else { // no keyframe after current position slotSetPosition(m_duration); } } void KeyframeView::slotGoToPrev() { if (m_position == 0) { return; } bool ok; auto prev = m_model->getPrevKeyframe(GenTime(m_position, pCore->getCurrentFps()), &ok); if (ok) { slotSetPosition(prev.first.frames(pCore->getCurrentFps())); } else { // no keyframe after current position slotSetPosition(m_duration); } } void KeyframeView::mousePressEvent(QMouseEvent *event) { int pos = event->x() / m_scale; if (event->y() < m_lineHeight && event->button() == Qt::LeftButton) { bool ok; GenTime position(pos, pCore->getCurrentFps()); auto keyframe = m_model->getClosestKeyframe(position, &ok); if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos) < 5) { m_currentKeyframeOriginal = keyframe.first.frames(pCore->getCurrentFps()); if (m_model->moveKeyframe(keyframe.first, position, false)) { m_currentKeyframe = pos; slotSetPosition(pos); return; } } } // no keyframe next to mouse m_currentKeyframe = m_currentKeyframeOriginal = -1; slotSetPosition(pos); update(); } void KeyframeView::mouseMoveEvent(QMouseEvent *event) { int pos = qBound(0, (int)(event->x() / m_scale), m_duration); GenTime position(pos, pCore->getCurrentFps()); if ((event->buttons() & Qt::LeftButton) != 0u) { if (m_currentKeyframe >= 0) { if (!m_model->hasKeyframe(pos)) { // snap to position cursor if (KdenliveSettings::snaptopoints() && qAbs(pos - m_position) < 5 && !m_model->hasKeyframe(m_position)) { pos = m_position; } GenTime currentPos(m_currentKeyframe, pCore->getCurrentFps()); if (m_model->moveKeyframe(currentPos, position, false)) { m_currentKeyframe = pos; } } } slotSetPosition(pos); return; } if (event->y() < m_lineHeight) { bool ok; auto keyframe = m_model->getClosestKeyframe(position, &ok); if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos) < 5) { m_hoverKeyframe = keyframe.first.frames(pCore->getCurrentFps()); setCursor(Qt::PointingHandCursor); update(); return; } } if (m_hoverKeyframe != -1) { m_hoverKeyframe = -1; setCursor(Qt::ArrowCursor); update(); } } void KeyframeView::mouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event) if (m_currentKeyframe >= 0) { GenTime initPos(m_currentKeyframeOriginal, pCore->getCurrentFps()); GenTime targetPos(m_currentKeyframe, pCore->getCurrentFps()); bool ok1 = m_model->moveKeyframe(targetPos, initPos, false); bool ok2 = m_model->moveKeyframe(initPos, targetPos, true); qDebug() << "RELEASING keyframe move"<getCurrentFps())<getCurrentFps()); } } void KeyframeView::mouseDoubleClickEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && event->y() < m_lineHeight) { int pos = qBound(0, (int)(event->x() / m_scale), m_duration); GenTime position(pos, pCore->getCurrentFps()); bool ok; auto keyframe = m_model->getClosestKeyframe(position, &ok); if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos) < 5) { m_model->removeKeyframe(keyframe.first); if (keyframe.first.frames(pCore->getCurrentFps()) == m_currentKeyframe) { m_currentKeyframe = m_currentKeyframeOriginal = -1; } if (keyframe.first.frames(pCore->getCurrentFps()) == m_position) { emit atKeyframe(false); } return; } // add new keyframe m_model->addKeyframe(position, m_currentType); } else { QWidget::mouseDoubleClickEvent(event); } } void KeyframeView::wheelEvent(QWheelEvent *event) { int change = event->delta() < 0 ? -1 : 1; int pos = qBound(0, m_position + change, m_duration); slotSetPosition(pos); } void KeyframeView::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QStylePainter p(this); m_scale = width() / (double)(m_duration); // p.translate(0, m_lineHeight); int headOffset = m_lineHeight / 1.5; /* * keyframes */ for (const auto &keyframe : *m_model.get()) { int pos = keyframe.first.frames(pCore->getCurrentFps()); if (pos == m_currentKeyframe || pos == m_hoverKeyframe) { p.setBrush(m_colSelected); } else { p.setBrush(m_colKeyframe); } int scaledPos = pos * m_scale; p.drawLine(scaledPos, headOffset, scaledPos, m_lineHeight + (headOffset / 2)); p.drawEllipse(scaledPos - headOffset / 2, 0, headOffset, headOffset); } p.setPen(palette().dark().color()); /* * Time-"line" */ p.setPen(m_colKeyframe); p.drawLine(0, m_lineHeight + (headOffset / 2), width(), m_lineHeight + (headOffset / 2)); /* * current position */ QPolygon pa(3); int cursorwidth = (m_size - (m_lineHeight + headOffset / 2)) / 2 + 1; QPolygonF position = QPolygonF() << QPointF(-cursorwidth, m_size) << QPointF(cursorwidth, m_size) << QPointF(0, m_lineHeight + (headOffset / 2) + 1); position.translate(m_position * m_scale, 0); p.setBrush(m_colKeyframe); p.drawPolygon(position); } diff --git a/src/assets/keyframes/view/keyframeview.hpp b/src/assets/keyframes/view/keyframeview.hpp index 1e87b8f3d..87802177d 100644 --- a/src/assets/keyframes/view/keyframeview.hpp +++ b/src/assets/keyframes/view/keyframeview.hpp @@ -1,86 +1,87 @@ /*************************************************************************** * Copyright (C) 2011 by Till Theato (root@ttill.de) * * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive (www.kdenlive.org). * * * * Kdenlive 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. * * * * Kdenlive 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 Kdenlive. If not, see . * ***************************************************************************/ #ifndef KEYFRAMEVIEW_H #define KEYFRAMEVIEW_H #include "assets/keyframes/model/keyframemodel.hpp" #include "assets/keyframes/model/keyframemodellist.hpp" #include #include class KeyframeModelList; class KeyframeView : public QWidget { Q_OBJECT public: explicit KeyframeView(std::shared_ptr model, QWidget *parent = nullptr); void setDuration(int dur); public slots: /* @brief moves the current position*/ void slotSetPosition(int pos); /* @brief remove the keyframe at given position If pos is negative, we remove keyframe at current position */ void slotRemoveKeyframe(int pos); /* @brief Add a keyframe with given parameter value at given pos. If pos is negative, then keyframe is added at current position */ void slotAddKeyframe(int pos = -1); /* @brief If there is a keyframe at current position, it is removed. Otherwise, we add a new one with given value. */ void slotAddRemove(); void slotGoToNext(); void slotGoToPrev(); void slotModelChanged(); protected: void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; private: std::shared_ptr m_model; int m_duration; int m_position; int m_currentKeyframe; int m_currentKeyframeOriginal; int m_hoverKeyframe; int m_lineHeight; double m_scale; int m_size; KeyframeType m_currentType; QColor m_colSelected; QColor m_colKeyframe; QColor m_colKeyframeBg; signals: void seekToPos(int pos); void atKeyframe(bool); + void modified(); }; #endif diff --git a/src/assets/view/widgets/keyframewidget.cpp b/src/assets/view/widgets/keyframewidget.cpp index 4d4155572..b0d74d211 100644 --- a/src/assets/view/widgets/keyframewidget.cpp +++ b/src/assets/view/widgets/keyframewidget.cpp @@ -1,155 +1,197 @@ /*************************************************************************** * Copyright (C) 2011 by Till Theato (root@ttill.de) * * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive (www.kdenlive.org). * * * * Kdenlive 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. * * * * Kdenlive 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 Kdenlive. If not, see . * ***************************************************************************/ #include "keyframewidget.hpp" #include "assets/keyframes/view/keyframeview.hpp" #include "assets/keyframes/model/keyframemodellist.hpp" #include "assets/model/assetparametermodel.hpp" #include "core.h" #include "monitor/monitor.h" #include "timecode.h" #include "timecodedisplay.h" #include "utils/KoIconUtils.h" +#include "widgets/doublewidget.h" #include #include #include KeyframeWidget::KeyframeWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(model, index, parent) { m_keyframes = model->getKeyframeModel(); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - auto *l = new QGridLayout(this); + m_lay = new QGridLayout(this); bool ok = false; int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); Q_ASSERT(ok); m_keyframeview = new KeyframeView(m_keyframes, this); m_keyframeview->setDuration(duration); m_buttonAddDelete = new QToolButton(this); m_buttonAddDelete->setAutoRaise(true); m_buttonAddDelete->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-add"))); m_buttonAddDelete->setToolTip(i18n("Add keyframe")); m_buttonPrevious = new QToolButton(this); m_buttonPrevious->setAutoRaise(true); m_buttonPrevious->setIcon(KoIconUtils::themedIcon(QStringLiteral("media-skip-backward"))); m_buttonPrevious->setToolTip(i18n("Go to previous keyframe")); m_buttonNext = new QToolButton(this); m_buttonNext->setAutoRaise(true); m_buttonNext->setIcon(KoIconUtils::themedIcon(QStringLiteral("media-skip-forward"))); m_buttonNext->setToolTip(i18n("Go to next keyframe")); m_time = new TimecodeDisplay(pCore->getMonitor(m_model->monitorId)->timecode(), this); m_time->setRange(0, duration); - l->addWidget(m_keyframeview, 0, 0, 1, -1); - l->addWidget(m_buttonPrevious, 1, 0); - l->addWidget(m_buttonAddDelete, 1, 1); - l->addWidget(m_buttonNext, 1, 2); - l->addWidget(m_time, 1, 3, Qt::AlignRight); + m_lay->addWidget(m_keyframeview, 0, 0, 1, -1); + m_lay->addWidget(m_buttonPrevious, 1, 0); + m_lay->addWidget(m_buttonAddDelete, 1, 1); + m_lay->addWidget(m_buttonNext, 1, 2); + m_lay->addWidget(m_time, 1, 3, Qt::AlignRight); + slotSetPosition(0, false); connect(m_time, &TimecodeDisplay::timeCodeEditingFinished, [&](){slotSetPosition(-1, true);}); connect(m_keyframeview, &KeyframeView::seekToPos, [&](int p){slotSetPosition(p, true);}); connect(m_keyframeview, &KeyframeView::atKeyframe, this, &KeyframeWidget::slotAtKeyframe); + connect(m_keyframeview, &KeyframeView::modified, this, &KeyframeWidget::slotRefreshParams); connect(m_buttonAddDelete, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotAddRemove); connect(m_buttonPrevious, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotGoToPrev); connect(m_buttonNext, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotGoToNext); + addParameter(index); } KeyframeWidget::~KeyframeWidget() { delete m_keyframeview; delete m_buttonAddDelete; delete m_buttonPrevious; delete m_buttonNext; delete m_time; } +void KeyframeWidget::slotRefreshParams() +{ + int pos = getPosition(); + for (const auto & w : m_parameters) { + w.second->setValue(m_keyframes->getInterpolatedValue(pos, w.first)); + } +} void KeyframeWidget::slotSetPosition(int pos, bool update) { if (pos < 0) { pos = m_time->getValue(); m_keyframeview->slotSetPosition(pos); } else { m_time->setValue(pos); m_keyframeview->slotSetPosition(pos); } + slotRefreshParams(); + if (update) { emit seekToPos(pos); } } int KeyframeWidget::getPosition() const { return m_time->getValue(); } void KeyframeWidget::addKeyframe(int pos) { blockSignals(true); m_keyframeview->slotAddKeyframe(pos); blockSignals(false); setEnabled(true); } void KeyframeWidget::updateTimecodeFormat() { m_time->slotUpdateTimeCodeFormat(); } void KeyframeWidget::slotAtKeyframe(bool atKeyframe) { if (atKeyframe) { m_buttonAddDelete->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-remove"))); m_buttonAddDelete->setToolTip(i18n("Delete keyframe")); } else { m_buttonAddDelete->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-add"))); m_buttonAddDelete->setToolTip(i18n("Add keyframe")); } + for (const auto &w : m_parameters) { + w.second->setEnabled(atKeyframe); + } } void KeyframeWidget::slotSetRange(QPair /*range*/) { bool ok = false; int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); Q_ASSERT(ok); m_keyframeview->setDuration(duration); m_time->setRange(0, duration); } void KeyframeWidget::slotRefresh() { // update duration slotSetRange(QPair(-1, -1)); // refresh keyframes m_keyframes->refresh(); } + +void KeyframeWidget::addParameter(const QPersistentModelIndex& index) +{ + QLocale locale; + locale.setNumberOptions(QLocale::OmitGroupSeparator); + // Retrieve parameters from the model + QString name = m_model->data(m_index, Qt::DisplayRole).toString(); + double value = m_keyframes->getInterpolatedValue(getPosition(), index); + double min = m_model->data(m_index, AssetParameterModel::MinRole).toDouble(); + double max = m_model->data(m_index, AssetParameterModel::MaxRole).toDouble(); + double defaultValue = locale.toDouble(m_model->data(m_index, AssetParameterModel::DefaultRole).toString()); + QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString(); + QString suffix = m_model->data(m_index, AssetParameterModel::SuffixRole).toString(); + int decimals = m_model->data(m_index, AssetParameterModel::DecimalsRole).toInt(); + double factor = m_model->data(m_index, AssetParameterModel::FactorRole).toDouble(); + // Construct object + auto doubleWidget = new DoubleWidget(name, value, min, max, defaultValue, comment, -1, suffix, decimals, this); + doubleWidget->factor = factor; + + m_parameters[index] = doubleWidget; + m_lay->addWidget(doubleWidget, 1 + (int)m_parameters.size(), 0, 1, -1); + + connect(doubleWidget, &DoubleWidget::valueChanged, [this, index](double v){ + m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), v, index); + }); +} diff --git a/src/assets/view/widgets/keyframewidget.hpp b/src/assets/view/widgets/keyframewidget.hpp index 6a06557ba..5465b9776 100644 --- a/src/assets/view/widgets/keyframewidget.hpp +++ b/src/assets/view/widgets/keyframewidget.hpp @@ -1,65 +1,76 @@ /*************************************************************************** * Copyright (C) 2011 by Till Theato (root@ttill.de) * * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive (www.kdenlive.org). * * * * Kdenlive 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. * * * * Kdenlive 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 Kdenlive. If not, see . * ***************************************************************************/ #ifndef KEYFRAMEWIDGET_H #define KEYFRAMEWIDGET_H #include "abstractparamwidget.hpp" #include #include +#include +#include "definitions.h" class AssetParameterModel; -class KeyframeModelList; +class DoubleWidget; class KeyframeView; +class KeyframeModelList; +class QGridLayout; class QToolButton; class TimecodeDisplay; class KeyframeWidget : public AbstractParamWidget { Q_OBJECT public: explicit KeyframeWidget(std::shared_ptr model, QModelIndex index, QWidget *parent = nullptr); ~KeyframeWidget(); + /* @brief Add a new parameter to be managed using the same keyframe viewer */ + void addParameter(const QPersistentModelIndex& index); int getPosition() const; void addKeyframe(int pos = -1); void updateTimecodeFormat(); void slotSetRange(QPair range) override; void slotRefresh() override; public slots: void slotSetPosition(int pos = -1, bool update = true); private slots: + /* brief Update the value of the widgets to reflect keyframe change */ + void slotRefreshParams(); void slotAtKeyframe(bool atKeyframe); private: + QGridLayout *m_lay; std::shared_ptr m_keyframes; KeyframeView *m_keyframeview; QToolButton *m_buttonAddDelete; QToolButton *m_buttonPrevious; QToolButton *m_buttonNext; TimecodeDisplay *m_time; + + std::unordered_map m_parameters; }; #endif diff --git a/src/widgets/doublewidget.h b/src/widgets/doublewidget.h index b64973c81..806958b56 100644 --- a/src/widgets/doublewidget.h +++ b/src/widgets/doublewidget.h @@ -1,85 +1,88 @@ /*************************************************************************** * Copyright (C) 2010 by Till Theato (root@ttill.de) * * * * 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 * ***************************************************************************/ #ifndef DOUBLEWIDGET_H #define DOUBLEWIDGET_H #include class DragValue; /** * @class DoubleWidget * @brief Widget to choose a double parameter (for a effect) with the help of a slider and a spinbox. * @author Till Theato * * The widget does only handle integers, so the parameter has to be converted into the proper double range afterwards. */ class DoubleWidget : public QWidget { Q_OBJECT public: /** @brief Sets up the parameter's GUI. * @param name Name of the parameter * @param value Value of the parameter * @param min Minimum value * @param max maximum value * @param defaultValue Value used when using reset functionality * @param comment A comment explaining the parameter. Will be shown in a tooltip. * @param suffix (optional) Suffix to display in spinbox * @param parent (optional) Parent Widget */ explicit DoubleWidget(const QString &name, double value, double min, double max, double defaultValue, const QString &comment, int id, const QString &suffix = QString(), int decimals = 0, QWidget *parent = nullptr); ~DoubleWidget(); /** @brief The factor by which real param value is multiplicated to give the slider value. */ double factor; /** @brief Gets the parameter's value. */ double getValue(); /** @brief Returns minimum size for QSpinBox, used to set all spinboxes to the same width. */ int spinSize(); void setSpinSize(int width); void enableEdit(bool enable); /** @brief Returns true if widget is currently being edited */ bool hasEditFocus() const; public slots: /** @brief Sets the value to @param value. */ void setValue(double value); /** @brief Sets value to m_default. */ void slotReset(); /** @brief Shows/Hides the comment label. */ void slotShowComment(bool show); private slots: void slotSetValue(double value, bool final); private: DragValue *m_dragVal; signals: void valueChanged(double); + + // same signal as valueChanged, but add an extra boolean to tell if user is dragging value or not + void valueChanging(double, bool); }; #endif