Commit 2ab2880a authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Display keyframes in timeline clip and allow move/delete

Has to be enabled through button in effectstack view
parent c0738086
......@@ -3,7 +3,7 @@
<name>Volume (keyframable)</name>
<description>Adjust audio volume with keyframes</description>
<author>Dan Dennedy</author>
<parameter type="animated" name="level" max="60" min="-60" default="0" suffix="dB">
<parameter type="keyframe" name="level" max="60" min="-60" default="0" suffix="dB">
<name>Gain</name>
</parameter>
</effect>
......@@ -30,6 +30,7 @@
#include "view/assetparameterview.hpp"
#include "utils/KoIconUtils.h"
#include "definitions.h"
#include "core.cpp"
#include <KColorScheme>
#include <KColorUtils>
......@@ -67,6 +68,15 @@ AssetPanel::AssetPanel(QWidget *parent)
m_splitButton->setVisible(false);
connect(m_splitButton, &QToolButton::toggled, this, &AssetPanel::processSplitEffect);
tLayout->addWidget(m_splitButton);
m_timelineButton = new QToolButton(this);
m_timelineButton->setIcon(KoIconUtils::themedIcon(QStringLiteral("adjustlevels")));
m_timelineButton->setToolTip(i18n("Display keyframes in timeline"));
m_timelineButton->setCheckable(true);
m_timelineButton->setVisible(false);
connect(m_timelineButton, &QToolButton::toggled, this, &AssetPanel::showKeyframes);
tLayout->addWidget(m_timelineButton);
m_lay->addLayout(tLayout);
m_lay->addWidget(m_transitionWidget);
m_lay->addWidget(m_effectStackWidget);
......@@ -87,7 +97,7 @@ void AssetPanel::showTransition(int tid, std::shared_ptr<AssetParameterModel> tr
m_transitionWidget->setModel(transitionModel, QPair<int, int>(-1, -1), QSize(), true);
}
void AssetPanel::showEffectStack(const QString &clipName, std::shared_ptr<EffectStackModel> effectsModel, QPair<int, int> range, QSize frameSize)
void AssetPanel::showEffectStack(const QString &clipName, std::shared_ptr<EffectStackModel> effectsModel, QPair<int, int> range, QSize frameSize, bool showKeyframes)
{
clear();
if (effectsModel == nullptr) {
......@@ -96,6 +106,8 @@ void AssetPanel::showEffectStack(const QString &clipName, std::shared_ptr<Effect
}
m_assetTitle->setText(i18n("%1 effects", clipName));
m_splitButton->setVisible(true);
m_timelineButton->setVisible(true);
m_timelineButton->setChecked(showKeyframes);
m_switchBuiltStack->setVisible(true);
m_effectStackWidget->setVisible(true);
m_effectStackWidget->setModel(effectsModel, range, frameSize);
......@@ -122,6 +134,7 @@ void AssetPanel::clear()
m_transitionWidget->unsetModel();
m_effectStackWidget->setVisible(false);
m_splitButton->setVisible(false);
m_timelineButton->setVisible(false);
m_switchBuiltStack->setVisible(false);
m_effectStackWidget->setProperty("clipId", QVariant());
m_effectStackWidget->unsetModel();
......@@ -210,6 +223,11 @@ void AssetPanel::processSplitEffect(bool enable)
}
}
void AssetPanel::showKeyframes(bool enable)
{
pCore->showClipKeyframes(m_effectStackWidget->stackOwner(), enable);
}
ObjectId AssetPanel::effectStackOwner()
{
if (!m_effectStackWidget->isVisible()) {
......
......@@ -53,7 +53,7 @@ public:
void showTransition(int tid, std::shared_ptr<AssetParameterModel> transition_model);
/* @brief Shows the parameters of the given effect stack model */
void showEffectStack(const QString &clipName, std::shared_ptr<EffectStackModel> effectsModel, QPair<int, int> range, QSize frameSize);
void showEffectStack(const QString &clipName, std::shared_ptr<EffectStackModel> effectsModel, QPair<int, int> range, QSize frameSize, bool showKeyframes);
/* @brief Clear the panel so that it doesn't display anything */
void clear();
......@@ -80,9 +80,12 @@ protected:
private:
QToolButton *m_switchBuiltStack;
QToolButton *m_splitButton;
QToolButton *m_timelineButton;
private slots:
void processSplitEffect(bool enable);
/** Displays the owner clip keyframes in timeline */
void showKeyframes(bool enable);
signals:
void doSplitEffect(bool);
......
......@@ -120,6 +120,13 @@ bool KeyframeModel::removeKeyframe(GenTime pos, Fun &undo, Fun &redo)
return false;
}
bool KeyframeModel::removeKeyframe(int frame)
{
GenTime pos(frame, pCore->getCurrentFps());
return removeKeyframe(pos);
}
bool KeyframeModel::removeKeyframe(GenTime pos)
{
QWriteLocker locker(&m_lock);
......@@ -166,6 +173,13 @@ bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, Fun &undo, Fun &re
return res;
}
bool KeyframeModel::moveKeyframe(int oldPos, int pos, bool logUndo)
{
GenTime oPos(oldPos, pCore->getCurrentFps());
GenTime nPos(pos, pCore->getCurrentFps());
return moveKeyframe(oPos, nPos, logUndo);
}
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo)
{
QWriteLocker locker(&m_lock);
......@@ -223,7 +237,7 @@ Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, QVarian
m_keyframeList[pos].first = type;
m_keyframeList[pos].second = value;
if (notify)
emit dataChanged(index(row), index(row), QVector<int>() << TypeRole << ValueRole);
emit dataChanged(index(row), index(row), {ValueRole,NormalizedValueRole});
return true;
};
}
......@@ -276,6 +290,7 @@ QHash<int, QByteArray> KeyframeModel::roleNames() const
roles[FrameRole] = "frame";
roles[TypeRole] = "type";
roles[ValueRole] = "value";
roles[NormalizedValueRole] = "normalizedValue";
return roles;
}
......@@ -293,6 +308,18 @@ QVariant KeyframeModel::data(const QModelIndex &index, int role) const
case Qt::EditRole:
case ValueRole:
return it->second.second;
case NormalizedValueRole: {
double val = it->second.second.toDouble();
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
return (val - min) / (max - min);
} else {
qDebug()<<"// CANNOT LOCK effect MODEL";
}
return 1;
}
case PosRole:
return it->first.seconds();
case FrameRole:
......@@ -317,6 +344,7 @@ bool KeyframeModel::singleKeyframe() const
return m_keyframeList.size() <= 1;
}
Keyframe KeyframeModel::getKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
......@@ -504,6 +532,10 @@ void KeyframeModel::parseAnimProperty(const QString &prop)
int frame;
mlt_keyframe_type type;
anim->key_get(i, frame, type);
if (!prop.contains(QLatin1Char('='))) {
//TODO: use a default user defined type
type = mlt_keyframe_linear;
}
QVariant value;
switch (m_paramType) {
case ParamType::AnimatedRect: {
......
......@@ -63,7 +63,7 @@ public:
*/
explicit KeyframeModel(std::weak_ptr<AssetParameterModel> model, const QModelIndex &index, std::weak_ptr<DocUndoStack> undo_stack, QObject *parent = nullptr);
enum { TypeRole = Qt::UserRole + 1, PosRole, FrameRole, ValueRole};
enum { TypeRole = Qt::UserRole + 1, PosRole, FrameRole, ValueRole, NormalizedValueRole};
friend class KeyframeModelList;
/* @brief Adds a keyframe at the given position. If there is already one then we update it.
......@@ -80,6 +80,7 @@ protected:
public:
/* @brief Removes the keyframe at the given position. */
Q_INVOKABLE bool removeKeyframe(int frame);
bool removeKeyframe(GenTime pos);
/* @brief Delete all the keyframes of the model */
bool removeAllKeyframes();
......@@ -95,6 +96,7 @@ public:
@param pos defines the new position of the keyframe, relative to the clip
@param logUndo if true, then an undo object is created
*/
Q_INVOKABLE bool moveKeyframe(int oldPos, int pos, bool logUndo);
bool moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo);
bool moveKeyframe(GenTime oldPos, GenTime pos, Fun &undo, Fun &redo);
......@@ -144,7 +146,7 @@ public:
QVariant getInterpolatedValue(const GenTime &pos) const;
// Mandatory overloads
QVariant data(const QModelIndex &index, int role) const override;
Q_INVOKABLE QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
......
......@@ -187,3 +187,12 @@ QVariant KeyframeModelList::getInterpolatedValue(int pos, const QPersistentModel
Q_ASSERT(m_parameters.count(index) > 0);
return m_parameters.at(index)->getInterpolatedValue(pos);
}
KeyframeModel *KeyframeModelList::getKeyModel()
{
if (m_parameters.size() > 0) {
return m_parameters.begin()->second.get();
}
return nullptr;
}
......@@ -119,6 +119,7 @@ public:
QVariant getInterpolatedValue(int pos, const QPersistentModelIndex& index) const;
void refresh();
Q_INVOKABLE KeyframeModel *getKeyModel();
protected:
......
......@@ -121,7 +121,7 @@ public:
/* @brief Returns the keyframe model associated with this asset
Return empty ptr if there is no keyframable parameter in the asset or if prepareKeyframes was not called
*/
std::shared_ptr<KeyframeModelList> getKeyframeModel();
Q_INVOKABLE std::shared_ptr<KeyframeModelList> getKeyframeModel();
/* @brief Must be called before using the keyframes of this model */
void prepareKeyframes();
......
......@@ -1439,7 +1439,7 @@ void Bin::selectProxyModel(const QModelIndex &id)
emit findInTimeline(QString());
emit requestClipShow(nullptr);
// clear effect stack
emit requestShowEffectStack(QString(), nullptr, QPair<int, int>(), QSize());
emit requestShowEffectStack(QString(), nullptr, QPair<int, int>(), QSize(), false);
// Display black bg in clip monitor
emit openClip(std::shared_ptr<ProjectClip>());
}
......@@ -2411,18 +2411,18 @@ void Bin::editMasterEffect(std::shared_ptr<AbstractProjectItem> clip)
if (clip) {
if (clip->itemType() == AbstractProjectItem::ClipItem) {
std::shared_ptr<ProjectClip>clp = std::static_pointer_cast<ProjectClip>(clip);
emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, QPair<int, int>(0, clp->frameDuration()), clp->getFrameSize());
emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, QPair<int, int>(0, clp->frameDuration()), clp->getFrameSize(), false);
return;
}
if (clip->itemType() == AbstractProjectItem::SubClipItem) {
if (auto ptr = clip->parentItem().lock()) {
std::shared_ptr<ProjectClip>clp = std::static_pointer_cast<ProjectClip>(ptr);
emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, QPair<int, int>(0, clp->frameDuration()), clp->getFrameSize());
emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, QPair<int, int>(0, clp->frameDuration()), clp->getFrameSize(), false);
}
return;
}
}
emit requestShowEffectStack(QString(), nullptr, QPair<int, int>(), QSize());
emit requestShowEffectStack(QString(), nullptr, QPair<int, int>(), QSize(), false);
}
void Bin::slotGotFocus()
......@@ -3412,7 +3412,7 @@ void Bin::setCurrent(std::shared_ptr<AbstractProjectItem> item)
case AbstractProjectItem::ClipItem: {
openProducer(std::static_pointer_cast<ProjectClip>(item));
std::shared_ptr<ProjectClip>clp = std::static_pointer_cast<ProjectClip>(item);
emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, QPair<int, int>(0, clp->frameDuration()), clp->getFrameSize());
emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, QPair<int, int>(0, clp->frameDuration()), clp->getFrameSize(), false);
break;
}
case AbstractProjectItem::SubClipItem: {
......
......@@ -520,7 +520,7 @@ signals:
/** @brief Trigger timecode format refresh where needed. */
void refreshTimeCode();
/** @brief Request display of effect stack for a Bin clip. */
void requestShowEffectStack(const QString &clipName, std::shared_ptr<EffectStackModel>, QPair<int, int> range, QSize frameSize);
void requestShowEffectStack(const QString &clipName, std::shared_ptr<EffectStackModel>, QPair<int, int> range, QSize frameSize, bool showKeyframes);
/** @brief Request that the given clip is displayed in the clip monitor */
void requestClipShow(std::shared_ptr<ProjectClip>);
void displayBinMessage(const QString &, KMessageWidget::MessageType);
......
......@@ -532,3 +532,18 @@ double Core::getClipSpeed(int id) const
{
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipSpeed(id);
}
void Core::updateItemKeyframes(ObjectId id)
{
if (id.first == ObjectType::TimelineClip) {
m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {TimelineModel::KeyframesRole});
}
}
void Core::showClipKeyframes(ObjectId id, bool enable)
{
if (id.first == ObjectType::TimelineClip) {
m_mainWindow->getCurrentTimeline()->controller()->showClipKeyframes(id.second, enable);
}
}
......@@ -155,6 +155,10 @@ public:
double getClipSpeed(int id) const;
void invalidateItem(ObjectId itemId);
void prepareShutdown();
/** the keyframe model changed (effect added, deleted, active effect changed), inform timeline */
void updateItemKeyframes(ObjectId id);
/** Show / hide keyframes for a timeline clip */
void showClipKeyframes(ObjectId id, bool enable);
private:
explicit Core();
......
......@@ -20,7 +20,7 @@
***************************************************************************/
#include "effectstackmodel.hpp"
#include "effects/effectsrepository.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "core.h"
#include "doc/docundostack.hpp"
#include "effectgroupmodel.hpp"
......@@ -87,6 +87,8 @@ void EffectStackModel::removeEffect(std::shared_ptr<EffectItemModel> effect)
QString effectName = EffectsRepository::get()->getName(effect->getAssetId());
PUSH_UNDO(undo, redo, i18n("Delete effect %1", effectName));
}
//TODO: integrate in undo/redo, change active effect
pCore->updateItemKeyframes(m_ownerId);
}
void EffectStackModel::copyEffect(std::shared_ptr<AbstractEffectItem> sourceItem)
......@@ -125,6 +127,8 @@ void EffectStackModel::appendEffect(const QString &effectId)
QString effectName = EffectsRepository::get()->getName(effectId);
PUSH_UNDO(undo, redo, i18n("Add effect %1", effectName));
}
//TODO: integrate in undo/redo, change active effect
pCore->updateItemKeyframes(m_ownerId);
}
bool EffectStackModel::adjustFadeLength(int duration, bool fromStart, bool audioFade, bool videoFade)
......@@ -310,6 +314,7 @@ void EffectStackModel::setActiveEffect(int ix)
if (ptr) {
ptr->set("kdenlive:activeeffect", ix);
}
pCore->updateItemKeyframes(m_ownerId);
}
int EffectStackModel::getActiveEffect() const
......@@ -416,3 +421,18 @@ double EffectStackModel::getFilter(const QString &effectId, const QString &param
return 0.0;
}
KeyframeModel *EffectStackModel::getEffectKeyframeModel()
{
if (rootItem->childCount() == 0) return nullptr;
auto ptr = m_service.lock();
int ix = 0;
if (ptr) {
ix = ptr->get_int("kdenlive:activeeffect");
}
std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel();
if (listModel) {
return listModel->getKeyModel();
}
return nullptr;
}
......@@ -38,6 +38,7 @@ class AssetParameterModel;
class DocUndoStack;
class EffectItemModel;
class TreeItem;
class KeyframeModel;
class EffectStackModel : public AbstractTreeModel
{
......@@ -91,6 +92,8 @@ public:
Q_INVOKABLE void adjust(const QString &effectId, const QString &effectName, double value);
Q_INVOKABLE bool hasFilter(const QString &effectId);
Q_INVOKABLE double getFilter(const QString &effectId, const QString &paramName);
/** get the active effect's keyframe model */
Q_INVOKABLE KeyframeModel *getEffectKeyframeModel();
public slots:
/* @brief Delete an effect from the stack */
......
......@@ -4131,6 +4131,7 @@ void MainWindow::slotChangeSpeed(int speed)
}
}
#ifdef DEBUG_MAINW
#undef DEBUG_MAINW
#endif
......@@ -55,13 +55,13 @@ Item {
onPaint:
{
if (context && root.centerPoints.length > 0) {
context.clearRect(0,0, width, height);
context.beginPath()
context.strokeStyle = Qt.rgba(1, 0, 0, 0.5)
context.fillStyle = Qt.rgba(1, 0, 0, 0.5)
context.lineWidth = 2
var p1 = convertPoint(root.centerPoints[0])
context.moveTo(p1.x, p1.y)
context.clearRect(0,0, width, height);
context.fillRect(p1.x - handleSize, p1.y - handleSize, 2 * handleSize, 2 * handleSize);
for(var i = 0; i < root.centerPoints.length; i++)
{
......
......@@ -364,3 +364,20 @@ double ClipModel::getSpeed() const
}
return 1.0;
}
KeyframeModel *ClipModel::getKeyframeModel()
{
return m_effectStack->getEffectKeyframeModel();
}
bool ClipModel::showKeyframes() const
{
READ_LOCK();
return service()->get_int("kdenlive:timeline_display");
}
void ClipModel::setShowKeyframes(bool show)
{
QWriteLocker locker(&m_lock);
service()->set("kdenlive:timeline_display", (int) show);
}
......@@ -35,6 +35,7 @@ class MarkerListModel;
class ProjectClip;
class TimelineModel;
class TrackModel;
class KeyframeModel;
/* @brief This class represents a Clip object, as viewed by the backend.
In general, the Gui associated with it will send modification queries (such as resize or move), and this class authorize them or not depending on the
......@@ -50,7 +51,6 @@ protected:
public:
~ClipModel();
/* @brief Creates a clip, which references itself to the parent timeline
Returns the (unique) id of the created clip
@param parent is a pointer to the timeline
......@@ -73,6 +73,8 @@ public:
int getIntProperty(const QString &name) const;
double getDoubleProperty(const QString &name) const;
QSize getFrameSize() const;
Q_INVOKABLE bool showKeyframes() const;
Q_INVOKABLE void setShowKeyframes(bool show);
/* @brief returns the length of the item on the timeline
*/
......@@ -92,6 +94,7 @@ public:
bool importEffects(std::shared_ptr<EffectStackModel> stackModel);
bool removeFade(bool fromStart);
bool adjustEffectLength(const QString &effectName, int duration);
KeyframeModel *getKeyframeModel();
int fadeIn() const;
int fadeOut() const;
......
......@@ -290,3 +290,9 @@ bool TimelineFunctions::requestClipCopy(std::shared_ptr<TimelineItemModel> timel
return true;
}
void TimelineFunctions::showClipKeyframes(std::shared_ptr<TimelineItemModel> timeline, int clipId, bool value)
{
timeline->m_allClips[clipId]->setShowKeyframes(value);
QModelIndex modelIndex = timeline->makeClipIndexFromID(clipId);
timeline->dataChanged(modelIndex, modelIndex, {TimelineModel::KeyframesRole});
}
......@@ -57,6 +57,7 @@ struct TimelineFunctions {
static bool insertZone(std::shared_ptr<TimelineItemModel> timeline, int trackId, const QString &binId, int insertFrame, QPoint zone, bool overwrite);
static bool requestClipCopy(std::shared_ptr<TimelineItemModel> timeline, int clipId, int trackId, int position);
static void showClipKeyframes(std::shared_ptr<TimelineItemModel> timeline, int clipId, bool value);
};
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment