diff --git a/src/assets/keyframes/view/keyframeview.cpp b/src/assets/keyframes/view/keyframeview.cpp
index 649ea3209..cf399135c 100644
--- a/src/assets/keyframes/view/keyframeview.cpp
+++ b/src/assets/keyframes/view/keyframeview.cpp
@@ -1,281 +1,283 @@
/***************************************************************************
* 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, [&](){
- emit atKeyframe(m_model->hasKeyframe(m_position));
- update();
- });
+ connect(m_model.get(), &KeyframeModelList::modelChanged, this, &KeyframeView::slotModelChanged);
}
+void KeyframeView::slotModelChanged()
+{
+ emit atKeyframe(m_model->hasKeyframe(m_position));
+ 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 9180149ca..1e87b8f3d 100644
--- a/src/assets/keyframes/view/keyframeview.hpp
+++ b/src/assets/keyframes/view/keyframeview.hpp
@@ -1,85 +1,86 @@
/***************************************************************************
* 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);
};
#endif
diff --git a/src/assets/model/assetparametermodel.cpp b/src/assets/model/assetparametermodel.cpp
index 23223fa80..e2dc4bb7f 100644
--- a/src/assets/model/assetparametermodel.cpp
+++ b/src/assets/model/assetparametermodel.cpp
@@ -1,357 +1,385 @@
/***************************************************************************
* 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 "assetparametermodel.hpp"
+#include "assets/keyframes/model/keyframemodellist.hpp"
#include "core.h"
#include "kdenlivesettings.h"
#include "klocalizedstring.h"
#include "profiles/profilemodel.hpp"
#include
#include
#include
AssetParameterModel::AssetParameterModel(Mlt::Properties *asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId, Kdenlive::MonitorId monitor,
QObject *parent)
: QAbstractListModel(parent)
, monitorId(monitor)
, m_xml(assetXml)
, m_assetId(assetId)
, m_ownerId(ownerId)
, m_asset(asset)
{
Q_ASSERT(asset->is_valid());
QDomNodeList nodeList = m_xml.elementsByTagName(QStringLiteral("parameter"));
bool needsLocaleConversion = false;
QChar separator, oldSeparator;
// Check locale
if (m_xml.hasAttribute(QStringLiteral("LC_NUMERIC"))) {
QLocale locale = QLocale(m_xml.attribute(QStringLiteral("LC_NUMERIC")));
if (locale.decimalPoint() != QLocale().decimalPoint()) {
needsLocaleConversion = true;
separator = QLocale().decimalPoint();
oldSeparator = locale.decimalPoint();
}
}
qDebug() << "XML parsing of " << assetId << ". found : " << nodeList.count();
for (int i = 0; i < nodeList.count(); ++i) {
QDomElement currentParameter = nodeList.item(i).toElement();
// Convert parameters if we need to
if (needsLocaleConversion) {
QDomNamedNodeMap attrs = currentParameter.attributes();
for (int k = 0; k < attrs.count(); ++k) {
QString nodeName = attrs.item(k).nodeName();
if (nodeName != QLatin1String("type") && nodeName != QLatin1String("name")) {
QString val = attrs.item(k).nodeValue();
if (val.contains(oldSeparator)) {
QString newVal = val.replace(oldSeparator, separator);
attrs.item(k).setNodeValue(newVal);
}
}
}
}
// Parse the basic attributes of the parameter
QString name = currentParameter.attribute(QStringLiteral("name"));
QString type = currentParameter.attribute(QStringLiteral("type"));
QString value = currentParameter.attribute(QStringLiteral("value"));
if (value.isNull()) {
value = parseAttribute(QStringLiteral("default"), currentParameter).toString();
}
bool isFixed = (type == QLatin1String("fixed"));
if (isFixed) {
m_fixedParams[name] = value;
}
qDebug() << "PARAMETER"<is_valid());
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
bool conversionSuccess;
double doubleValue = locale.toDouble(value, &conversionSuccess);
if (conversionSuccess) {
m_asset->set(name.toLatin1().constData(), doubleValue);
if (m_fixedParams.count(name) == 0) {
m_params[name].value = doubleValue;
} else {
m_fixedParams[name] = doubleValue;
}
} else {
m_asset->set(name.toLatin1().constData(), value.toUtf8().constData());
if (m_fixedParams.count(name) == 0) {
m_params[name].value = value;
} else {
m_fixedParams[name] = value;
}
}
pCore->refreshProjectItem(m_ownerId);
pCore->invalidateItem(m_ownerId);
}
void AssetParameterModel::setParameter(const QString &name, double &value)
{
Q_ASSERT(m_asset->is_valid());
m_asset->set(name.toLatin1().constData(), value);
if (m_fixedParams.count(name) == 0) {
m_params[name].value = value;
} else {
m_fixedParams[name] = value;
}
pCore->refreshProjectItem(m_ownerId);
pCore->invalidateItem(m_ownerId);
}
AssetParameterModel::~AssetParameterModel() = default;
QVariant AssetParameterModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_rows.size() || !index.isValid()) {
return QVariant();
}
QString paramName = m_rows[index.row()];
Q_ASSERT(m_params.count(paramName) > 0);
const QDomElement &element = m_params.at(paramName).xml;
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
return m_params.at(paramName).name;
case NameRole:
return paramName;
case TypeRole:
return QVariant::fromValue(m_params.at(paramName).type);
case CommentRole: {
QDomElement commentElem = element.firstChildElement(QStringLiteral("comment"));
QString comment;
if (!commentElem.isNull()) {
comment = i18n(commentElem.text().toUtf8().data());
}
return comment;
}
case InRole:
return m_asset->get_int("in");
case OutRole:
return m_asset->get_int("out");
case ParentInRole:
return pCore->getItemIn(m_ownerId);
case ParentDurationRole:
return pCore->getItemDuration(m_ownerId);
case MinRole:
return parseAttribute(QStringLiteral("min"), element);
case MaxRole:
return parseAttribute(QStringLiteral("max"), element);
case FactorRole:
return parseAttribute(QStringLiteral("factor"), element, 1);
case DecimalsRole:
return parseAttribute(QStringLiteral("decimals"), element);
case DefaultRole:
return parseAttribute(QStringLiteral("default"), element);
case SuffixRole:
return element.attribute(QStringLiteral("suffix"));
case OpacityRole:
return element.attribute(QStringLiteral("opacity")) != QLatin1String("false");
case ValueRole: {
QString value = m_asset->get(paramName.toUtf8().constData());
return value.isEmpty() ? (element.attribute(QStringLiteral("value")).isNull() ? parseAttribute(QStringLiteral("default"), element)
: element.attribute(QStringLiteral("value")))
: value;
}
case ListValuesRole:
return element.attribute(QStringLiteral("paramlist")).split(QLatin1Char(';'));
case ListNamesRole: {
QDomElement namesElem = element.firstChildElement(QStringLiteral("paramlistdisplay"));
return i18n(namesElem.text().toUtf8().data()).split(QLatin1Char(','));
}
}
return QVariant();
}
int AssetParameterModel::rowCount(const QModelIndex &parent) const
{
qDebug() << "===================================================== Requested rowCount" << parent << m_rows.size();
if (parent.isValid()) return 0;
return m_rows.size();
}
// static
ParamType AssetParameterModel::paramTypeFromStr(const QString &type)
{
if (type == QLatin1String("double") || type == QLatin1String("float") || type == QLatin1String("constant")) {
return ParamType::Double;
}
if (type == QLatin1String("list")) {
return ParamType::List;
}
if (type == QLatin1String("bool")) {
return ParamType::Bool;
}
if (type == QLatin1String("switch")) {
return ParamType::Switch;
} else if (type == QLatin1String("simplekeyframe")) {
return ParamType::RestrictedAnim;
} else if (type == QLatin1String("animated")) {
return ParamType::Animated;
} else if (type == QLatin1String("animatedrect")) {
return ParamType::AnimatedRect;
} else if (type == QLatin1String("geometry")) {
return ParamType::Geometry;
} else if (type == QLatin1String("addedgeometry")) {
return ParamType::Addedgeometry;
} else if (type == QLatin1String("keyframe")) {
return ParamType::KeyframeParam;
} else if (type == QLatin1String("color")) {
return ParamType::Color;
} else if (type == QLatin1String("position")) {
return ParamType::Position;
} else if (type == QLatin1String("curve")) {
return ParamType::Curve;
} else if (type == QLatin1String("bezier_spline")) {
return ParamType::Bezier_spline;
} else if (type == QLatin1String("roto-spline")) {
return ParamType::Roto_spline;
} else if (type == QLatin1String("wipe")) {
return ParamType::Wipe;
} else if (type == QLatin1String("url")) {
return ParamType::Url;
} else if (type == QLatin1String("keywords")) {
return ParamType::Keywords;
} else if (type == QLatin1String("fontfamily")) {
return ParamType::Fontfamily;
} else if (type == QLatin1String("filterjob")) {
return ParamType::Filterjob;
} else if (type == QLatin1String("readonly")) {
return ParamType::Readonly;
}
qDebug() << "WARNING: Unknown type :" << type;
return ParamType::Double;
}
// static
QString AssetParameterModel::getDefaultKeyframes(int start, const QString &defaultValue, bool linearOnly)
{
QString keyframes = QString::number(start);
if (linearOnly) {
keyframes.append(QLatin1Char('='));
} else {
switch (KdenliveSettings::defaultkeyframeinterp()) {
case mlt_keyframe_discrete:
keyframes.append(QStringLiteral("|="));
break;
case mlt_keyframe_smooth:
keyframes.append(QStringLiteral("~="));
break;
default:
keyframes.append(QLatin1Char('='));
break;
}
}
keyframes.append(defaultValue);
return keyframes;
}
// static
QVariant AssetParameterModel::parseAttribute(const QString &attribute, const QDomElement &element, QVariant defaultValue)
{
if (!element.hasAttribute(attribute) && !defaultValue.isNull()) {
return defaultValue;
}
ParamType type = paramTypeFromStr(element.attribute(QStringLiteral("type")));
QString content = element.attribute(attribute);
if (content.contains(QLatin1Char('%'))) {
std::unique_ptr &profile = pCore->getCurrentProfile();
int width = profile->width();
int height = profile->height();
// replace symbols in the double parameter
content.replace(QLatin1String("%maxWidth"), QString::number(width))
.replace(QLatin1String("%maxHeight"), QString::number(height))
.replace(QLatin1String("%width"), QString::number(width))
.replace(QLatin1String("%height"), QString::number(height));
if (type == ParamType::Double) {
// Use a Mlt::Properties to parse mathematical operators
Mlt::Properties p;
p.set("eval", content.toLatin1().constData());
return p.get_double("eval");
}
} else if (type == ParamType::Double) {
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
return locale.toDouble(content);
}
if (attribute == QLatin1String("default")) {
if (type == ParamType::RestrictedAnim) {
content = getDefaultKeyframes(0, content, true);
}
}
return content;
}
QString AssetParameterModel::getAssetId() const
{
return m_assetId;
}
QVector> AssetParameterModel::getAllParameters() const
{
QVector> res;
res.reserve((int)m_fixedParams.size() + (int)m_params.size());
for (const auto &fixed : m_fixedParams) {
res.push_back(QPair(fixed.first, fixed.second));
}
for (const auto ¶m : m_params) {
res.push_back(QPair(param.first, param.second.value));
}
return res;
}
void AssetParameterModel::setParameters(const QVector> ¶ms)
{
for (const auto ¶m : params) {
setParameter(param.first, param.second.toString());
}
}
ObjectId AssetParameterModel::getOwnerId() const
{
return m_ownerId;
}
+
+void AssetParameterModel::addKeyframeParam(const QModelIndex index)
+{
+ if (m_keyframes) {
+ m_keyframes->addParameter(index);
+ } else {
+ m_keyframes.reset(new KeyframeModelList(shared_from_this(), index, pCore->undoStack()));
+ }
+}
+
+std::shared_ptr AssetParameterModel::getKeyframeModel()
+{
+ return m_keyframes;
+}
+
diff --git a/src/assets/model/assetparametermodel.hpp b/src/assets/model/assetparametermodel.hpp
index 2dea29954..9e0e9ed24 100644
--- a/src/assets/model/assetparametermodel.hpp
+++ b/src/assets/model/assetparametermodel.hpp
@@ -1,150 +1,167 @@
/***************************************************************************
* 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 ASSETPARAMETERMODEL_H
#define ASSETPARAMETERMODEL_H
#include "definitions.h"
#include "klocalizedstring.h"
#include
#include
#include
#include
#include
#include
+class KeyframeModelList;
/* @brief This class is the model for a list of parameters.
The behaviour of a transition or an effect is typically controlled by several parameters. This class exposes this parameters as a list that can be rendered
using the relevant widgets.
Note that internally parameters are not sorted in any ways, because some effects like sox need a precise order
*/
enum class ParamType {
Double,
List,
Bool,
Switch,
RestrictedAnim, // animated 1 dimensional param with linear support only
Animated,
AnimatedRect,
Geometry,
Addedgeometry,
KeyframeParam,
Color,
Position,
Curve,
Bezier_spline,
Roto_spline,
Wipe,
Url,
Keywords,
Fontfamily,
Filterjob,
Readonly
};
Q_DECLARE_METATYPE(ParamType)
class AssetParameterModel : public QAbstractListModel, public enable_shared_from_this_virtual
{
Q_OBJECT
public:
explicit AssetParameterModel(Mlt::Properties *asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId,
Kdenlive::MonitorId monitor = Kdenlive::ProjectMonitor, QObject *parent = nullptr);
virtual ~AssetParameterModel();
enum {
NameRole = Qt::UserRole + 1,
TypeRole,
CommentRole,
MinRole,
MaxRole,
DefaultRole,
SuffixRole,
DecimalsRole,
ValueRole,
ListValuesRole,
ListNamesRole,
FactorRole,
OpacityRole,
InRole,
OutRole,
ParentInRole,
ParentDurationRole
};
/* @brief Returns the id of the asset represented by this object */
QString getAssetId() const;
/* @brief Set the parameter with given name to the given value
*/
Q_INVOKABLE void setParameter(const QString &name, const QString &value);
Q_INVOKABLE void setParameter(const QString &name, double &value);
/* @brief Return all the parameters as pairs (parameter name, parameter value) */
QVector> getAllParameters() const;
/* @brief Sets the value of a list of parameters
@param params contains the pairs (parameter name, parameter value)
*/
void setParameters(const QVector> ¶ms);
/* Which monitor is attached to this asset (clip/project)
*/
Kdenlive::MonitorId monitorId;
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/* @brief Returns the id of the actual object associated with this asset */
ObjectId getOwnerId() const;
+ /* @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 getKeyframeModel();
+
+ /* @brief Must be called before using the keyframes of this model */
+ void prepareKeyframes();
+
protected:
/* @brief Helper function to retrieve the type of a parameter given the string corresponding to it*/
static ParamType paramTypeFromStr(const QString &type);
static QString getDefaultKeyframes(int start, const QString &defaultValue, bool linearOnly);
/* @brief Helper function to get an attribute from a dom element, given its name.
The function additionally parses following keywords:
- %width and %height that are replaced with profile's height and width.
If keywords are found, mathematical operations are supported for double type params. For example "%width -1" is a valid value.
*/
static QVariant parseAttribute(const QString &attribute, const QDomElement &element, QVariant defaultValue = QVariant());
+
+ /* @brief Helper function to register one more parameter that is keyframable.
+ @param index is the index corresponding to this parameter
+ */
+ void addKeyframeParam(const QModelIndex index);
+
struct ParamRow
{
ParamType type;
QDomElement xml;
QVariant value;
QString name;
};
QDomElement m_xml;
QString m_assetId;
ObjectId m_ownerId;
std::unordered_map m_params; // Store all parameters by name
std::unordered_map m_fixedParams; // We store values of fixed parameters aside
QVector m_rows; // We store the params name in order of parsing. The order is important (cf some effects like sox)
std::unique_ptr m_asset;
+
+ std::shared_ptr m_keyframes;
};
#endif
diff --git a/src/assets/view/assetparameterview.cpp b/src/assets/view/assetparameterview.cpp
index efb316a3d..0080d15e5 100644
--- a/src/assets/view/assetparameterview.cpp
+++ b/src/assets/view/assetparameterview.cpp
@@ -1,158 +1,159 @@
/***************************************************************************
* 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 "assetparameterview.hpp"
#include "assets/model/assetcommand.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "assets/view/widgets/abstractparamwidget.hpp"
#include "core.h"
#include "widgets/animationwidget.h"
#include
#include
#include
#include
#include
AssetParameterView::AssetParameterView(QWidget *parent)
: QWidget(parent)
{
m_lay = new QVBoxLayout(this);
m_lay->setContentsMargins(2, 2, 2, 2);
m_lay->setSpacing(2);
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
}
void AssetParameterView::setModel(const std::shared_ptr &model, QPair range, bool addSpacer)
{
qDebug() << "set model " << model.get();
unsetModel();
QMutexLocker lock(&m_lock);
m_model = model;
+ m_model->prepareKeyframes();
connect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh);
AnimationWidget *animWidget = nullptr;
for (int i = 0; i < model->rowCount(); ++i) {
QModelIndex index = model->index(i, 0);
auto type = model->data(index, AssetParameterModel::TypeRole).value();
if (animWidget && (type == ParamType::Geometry || type == ParamType::Animated || type == ParamType::RestrictedAnim)) {
// Animation widget can have some extra params that should'nt build a new widget
// TODO refac
// animWidget->addParameter(index);
} else {
auto w = AbstractParamWidget::construct(model, index, range, this);
if (type == ParamType::Geometry || type == ParamType::Animated || type == ParamType::RestrictedAnim || type == ParamType::AnimatedRect) {
animWidget = static_cast(w);
}
connect(w, &AbstractParamWidget::valueChanged, this, &AssetParameterView::commitChanges);
connect(w, &AbstractParamWidget::seekToPos, this, &AssetParameterView::seekToPos);
m_lay->addWidget(w);
m_widgets.push_back(w);
}
}
if (addSpacer) {
m_lay->addStretch();
}
}
void AssetParameterView::resetValues()
{
QMutexLocker lock(&m_lock);
for (int i = 0; i < m_model->rowCount(); ++i) {
QModelIndex index = m_model->index(i, 0);
QString name = m_model->data(index, AssetParameterModel::NameRole).toString();
QString defaultValue = m_model->data(index, AssetParameterModel::DefaultRole).toString();
m_model->setParameter(name, defaultValue);
refresh(index, index, QVector());
}
}
void AssetParameterView::setRange(QPair range)
{
qDebug() << "SETTING RANGE"<slotSetRange(range);
}
}
void AssetParameterView::commitChanges(const QModelIndex &index, const QString &value, bool storeUndo)
{
// Warning: please note that some widgets (for example keyframes) do NOT send the valueChanged signal and do modifications on their own
AssetCommand *command = new AssetCommand(m_model, index, value);
if (storeUndo) {
pCore->pushUndo(command);
} else {
command->redo();
delete command;
}
}
void AssetParameterView::unsetModel()
{
QMutexLocker lock(&m_lock);
if (m_model) {
// if a model is already there, we have to disconnect signals first
disconnect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh);
}
// clear layout
m_widgets.clear();
QLayoutItem *child;
while ((child = m_lay->takeAt(0)) != nullptr) {
if (child->layout()) {
QLayoutItem *subchild;
while ((subchild = child->layout()->takeAt(0)) != nullptr) {
delete subchild->widget();
delete subchild->spacerItem();
}
}
delete child->widget();
delete child->spacerItem();
}
// Release ownership of smart pointer
m_model.reset();
}
void AssetParameterView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles)
{
QMutexLocker lock(&m_lock);
if (m_widgets.size() == 0) {
// no visible param for this asset, abort
return;
}
Q_UNUSED(roles);
// We are expecting indexes that are children of the root index, which is "invalid"
Q_ASSERT(!topLeft.parent().isValid());
// We make sure the range is valid
Q_ASSERT(bottomRight.row() < (int)m_widgets.size());
for (auto i = (size_t)topLeft.row(); i <= (size_t)bottomRight.row(); ++i) {
m_widgets[i]->slotRefresh();
}
}
int AssetParameterView::contentHeight() const
{
return m_lay->sizeHint().height();
}
diff --git a/src/assets/view/widgets/keyframewidget.cpp b/src/assets/view/widgets/keyframewidget.cpp
index e417ce021..4d4155572 100644
--- a/src/assets/view/widgets/keyframewidget.cpp
+++ b/src/assets/view/widgets/keyframewidget.cpp
@@ -1,155 +1,155 @@
/***************************************************************************
* 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
#include
#include
KeyframeWidget::KeyframeWidget(std::shared_ptr model, QModelIndex index, QWidget *parent)
: AbstractParamWidget(model, index, parent)
{
- m_keyframes = std::shared_ptr(new KeyframeModelList(model, index, pCore->undoStack()));
+ m_keyframes = model->getKeyframeModel();
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
auto *l = 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);
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_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);
}
KeyframeWidget::~KeyframeWidget()
{
delete m_keyframeview;
delete m_buttonAddDelete;
delete m_buttonPrevious;
delete m_buttonNext;
delete m_time;
}
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);
}
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"));
}
}
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();
}
diff --git a/src/effects/effectstack/view/collapsibleeffectview.cpp b/src/effects/effectstack/view/collapsibleeffectview.cpp
index 02f37907a..5f0d5987f 100644
--- a/src/effects/effectstack/view/collapsibleeffectview.cpp
+++ b/src/effects/effectstack/view/collapsibleeffectview.cpp
@@ -1,807 +1,808 @@
/***************************************************************************
* Copyright (C) 2017 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "collapsibleeffectview.hpp"
#include "assets/view/assetparameterview.hpp"
#include "core.h"
#include "dialogs/clipcreationdialog.h"
#include "effects/effectsrepository.hpp"
#include "effects/effectstack/model/effectitemmodel.hpp"
#include "effectslist/effectslist.h"
#include "kdenlivesettings.h"
#include "mltcontroller/effectscontroller.h"
#include "utils/KoIconUtils.h"
#include "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
CollapsibleEffectView::CollapsibleEffectView(std::shared_ptr effectModel, QPair range, QImage icon, QWidget *parent)
: AbstractCollapsibleWidget(parent)
/* , m_effect(effect)
, m_itemInfo(info)
, m_original_effect(original_effect)
, m_isMovable(true)*/
, m_model(effectModel)
, m_view(nullptr)
, m_regionEffect(false)
{
QString effectId = effectModel->getAssetId();
QString effectName = EffectsRepository::get()->getName(effectId);
if (effectId == QLatin1String("region")) {
m_regionEffect = true;
decoframe->setObjectName(QStringLiteral("decoframegroup"));
}
filterWheelEvent = true;
// decoframe->setProperty("active", true);
// m_info.fromString(effect.attribute(QStringLiteral("kdenlive_info")));
// setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
buttonUp->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-up")));
QSize iconSize = buttonUp->iconSize();
buttonUp->setMaximumSize(iconSize);
buttonDown->setMaximumSize(iconSize);
menuButton->setMaximumSize(iconSize);
enabledButton->setMaximumSize(iconSize);
buttonDel->setMaximumSize(iconSize);
buttonUp->setToolTip(i18n("Move effect up"));
buttonDown->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-down")));
buttonDown->setToolTip(i18n("Move effect down"));
buttonDel->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-deleffect")));
buttonDel->setToolTip(i18n("Delete effect"));
// buttonUp->setEnabled(canMoveUp);
// buttonDown->setEnabled(!lastEffect);
if (effectId == QLatin1String("speed")) {
// Speed effect is a "pseudo" effect, cannot be moved
buttonUp->setVisible(false);
buttonDown->setVisible(false);
m_isMovable = false;
setAcceptDrops(false);
} else {
setAcceptDrops(true);
}
// checkAll->setToolTip(i18n("Enable/Disable all effects"));
// buttonShowComments->setIcon(KoIconUtils::themedIcon("help-about"));
// buttonShowComments->setToolTip(i18n("Show additional information for the parameters"));
m_collapse = new KDualAction(i18n("Collapse Effect"), i18n("Expand Effect"), this);
m_collapse->setActiveIcon(KoIconUtils::themedIcon(QStringLiteral("arrow-right")));
m_collapse->setInactiveIcon(KoIconUtils::themedIcon(QStringLiteral("arrow-down")));
collapseButton->setDefaultAction(m_collapse);
connect(m_collapse, &KDualAction::activeChanged, this, &CollapsibleEffectView::slotSwitch);
QHBoxLayout *l = static_cast(frame->layout());
m_colorIcon = new QLabel(this);
l->insertWidget(0, m_colorIcon);
m_colorIcon->setFixedSize(icon.size());
title = new QLabel(this);
l->insertWidget(2, title);
m_enabledButton = new KDualAction(i18n("Disable Effect"), i18n("Enable Effect"), this);
m_enabledButton->setActiveIcon(KoIconUtils::themedIcon(QStringLiteral("hint")));
m_enabledButton->setInactiveIcon(KoIconUtils::themedIcon(QStringLiteral("visibility")));
enabledButton->setDefaultAction(m_enabledButton);
m_groupAction = new QAction(KoIconUtils::themedIcon(QStringLiteral("folder-new")), i18n("Create Group"), this);
connect(m_groupAction, &QAction::triggered, this, &CollapsibleEffectView::slotCreateGroup);
if (m_regionEffect) {
effectName.append(':' + QUrl(EffectsList::parameter(m_effect, QStringLiteral("resource"))).fileName());
}
// Color thumb
m_colorIcon->setPixmap(QPixmap::fromImage(icon));
title->setText(effectName);
m_view = new AssetParameterView(this);
m_view->setModel(std::static_pointer_cast(effectModel), range);
connect(m_view, &AssetParameterView::seekToPos, this, &AbstractCollapsibleWidget::seekToPos);
QVBoxLayout *lay = new QVBoxLayout(widgetFrame);
lay->setContentsMargins(0, 0, 0, 0);
lay->setSpacing(0);
lay->addWidget(m_view);
m_menu = new QMenu(this);
if (effectModel->rowCount() > 0) {
m_menu->addAction(KoIconUtils::themedIcon(QStringLiteral("view-refresh")), i18n("Reset Effect"), this, SLOT(slotResetEffect()));
} else {
collapseButton->setEnabled(false);
m_view->setVisible(false);
}
m_menu->addAction(KoIconUtils::themedIcon(QStringLiteral("document-save")), i18n("Save Effect"), this, SLOT(slotSaveEffect()));
if (!m_regionEffect) {
if (m_info.groupIndex == -1) {
m_menu->addAction(m_groupAction);
}
m_menu->addAction(KoIconUtils::themedIcon(QStringLiteral("folder-new")), i18n("Create Region"), this, SLOT(slotCreateRegion()));
}
// setupWidget(info, metaInfo);
menuButton->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-menu")));
menuButton->setMenu(m_menu);
if (!effectModel->isEnabled()) {
title->setEnabled(false);
m_colorIcon->setEnabled(false);
if (KdenliveSettings::disable_effect_parameters()) {
widgetFrame->setEnabled(false);
}
m_enabledButton->setActive(true);
} else {
m_enabledButton->setActive(false);
}
connect(m_enabledButton, SIGNAL(activeChangedByUser(bool)), this, SLOT(slotDisable(bool)));
connect(buttonUp, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotEffectUp);
connect(buttonDown, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotEffectDown);
connect(buttonDel, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotDeleteEffect);
Q_FOREACH (QSpinBox *sp, findChildren()) {
sp->installEventFilter(this);
sp->setFocusPolicy(Qt::StrongFocus);
}
Q_FOREACH (KComboBox *cb, findChildren()) {
cb->installEventFilter(this);
cb->setFocusPolicy(Qt::StrongFocus);
}
Q_FOREACH (QProgressBar *cb, findChildren()) {
cb->installEventFilter(this);
cb->setFocusPolicy(Qt::StrongFocus);
}
}
CollapsibleEffectView::~CollapsibleEffectView()
{
- // delete m_paramWidget;
+ qDebug() << "deleting collapsibleeffectview";
+ delete m_view;
delete m_menu;
}
void CollapsibleEffectView::setWidgetHeight(qreal value)
{
widgetFrame->setFixedHeight(m_view->contentHeight() * value);
}
void CollapsibleEffectView::slotCreateGroup()
{
emit createGroup(m_model);
}
void CollapsibleEffectView::slotCreateRegion()
{
QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' '));
const QString dialogFilter =
allExtensions + QLatin1Char(' ') + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n* ") + QLatin1Char('|') + i18n("All Files");
QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
if (clipFolder.isEmpty()) {
clipFolder = QDir::homePath();
}
QPointer d = new QFileDialog(QApplication::activeWindow(), QString(), clipFolder, dialogFilter);
d->setFileMode(QFileDialog::ExistingFile);
if (d->exec() == QDialog::Accepted && !d->selectedUrls().isEmpty()) {
KRecentDirs::add(QStringLiteral(":KdenliveClipFolder"), d->selectedUrls().first().adjusted(QUrl::RemoveFilename).toLocalFile());
emit createRegion(effectIndex(), d->selectedUrls().first());
}
delete d;
}
void CollapsibleEffectView::slotUnGroup()
{
emit unGroup(this);
}
bool CollapsibleEffectView::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Enter) {
frame->setProperty("mouseover", true);
frame->setStyleSheet(frame->styleSheet());
return QWidget::eventFilter(o, e);
}
if (e->type() == QEvent::Wheel) {
QWheelEvent *we = static_cast(e);
if (!filterWheelEvent || we->modifiers() != Qt::NoModifier) {
e->accept();
return false;
}
if (qobject_cast(o)) {
if (qobject_cast(o)->focusPolicy() == Qt::WheelFocus) {
e->accept();
return false;
}
e->ignore();
return true;
}
if (qobject_cast(o)) {
if (qobject_cast(o)->focusPolicy() == Qt::WheelFocus) {
e->accept();
return false;
}
e->ignore();
return true;
}
if (qobject_cast(o)) {
if (qobject_cast(o)->focusPolicy() == Qt::WheelFocus) {
e->accept();
return false;
}
e->ignore();
return true;
}
}
return QWidget::eventFilter(o, e);
}
QDomElement CollapsibleEffectView::effect() const
{
return m_effect;
}
QDomElement CollapsibleEffectView::effectForSave() const
{
QDomElement effect = m_effect.cloneNode().toElement();
effect.removeAttribute(QStringLiteral("kdenlive_ix"));
/*
if (m_paramWidget) {
int in = m_paramWidget->range().x();
EffectsController::offsetKeyframes(in, effect);
}
*/
return effect;
}
bool CollapsibleEffectView::isActive() const
{
return decoframe->property("active").toBool();
}
bool CollapsibleEffectView::isEnabled() const
{
return m_enabledButton->isActive();
}
void CollapsibleEffectView::slotActivateEffect(QModelIndex ix)
{
decoframe->setProperty("active", ix.row() == m_model->row());
decoframe->setStyleSheet(decoframe->styleSheet());
}
void CollapsibleEffectView::setActive(bool activate)
{
/*
decoframe->setProperty("active", activate);
decoframe->setStyleSheet(decoframe->styleSheet());
if (m_paramWidget) {
m_paramWidget->connectMonitor(activate);
}
if (activate) {
m_colorIcon->setPixmap(m_iconPix);
} else {
// desaturate icon
QPixmap alpha = m_iconPix;
QPainter p(&alpha);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(alpha.rect(), QColor(80, 80, 80, 80));
p.end();
m_colorIcon->setPixmap(alpha);
}
*/
}
void CollapsibleEffectView::mousePressEvent(QMouseEvent *e)
{
m_dragStart = e->globalPos();
emit activateEffect(m_model);
QWidget::mousePressEvent(e);
}
void CollapsibleEffectView::mouseMoveEvent(QMouseEvent *e)
{
if ((e->globalPos() - m_dragStart).manhattanLength() < QApplication::startDragDistance()) {
QPixmap pix = frame->grab();
emit startDrag(pix, m_model);
}
QWidget::mouseMoveEvent(e);
}
void CollapsibleEffectView::mouseDoubleClickEvent(QMouseEvent *event)
{
if (frame->underMouse() && collapseButton->isEnabled()) {
event->accept();
m_collapse->setActive(!m_collapse->isActive());
} else {
event->ignore();
}
}
void CollapsibleEffectView::mouseReleaseEvent(QMouseEvent *event)
{
m_dragStart = QPoint();
if (!decoframe->property("active").toBool()) {
// emit activateEffect(effectIndex());
}
QWidget::mouseReleaseEvent(event);
}
void CollapsibleEffectView::slotDisable(bool disable)
{
QString effectId = m_model->getAssetId();
QString effectName = EffectsRepository::get()->getName(effectId);
std::static_pointer_cast(m_model)->markEnabled(effectName, !disable);
}
void CollapsibleEffectView::slotDeleteEffect()
{
emit deleteEffect(m_model);
}
void CollapsibleEffectView::slotEffectUp()
{
emit moveEffect(qMax(0, m_model->row() - 1), m_model);
}
void CollapsibleEffectView::slotEffectDown()
{
emit moveEffect(m_model->row() + 1, m_model);
}
void CollapsibleEffectView::slotSaveEffect()
{
QString name = QInputDialog::getText(this, i18n("Save Effect"), i18n("Name for saved effect: "));
if (name.trimmed().isEmpty()) {
return;
}
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/"));
if (!dir.exists()) {
dir.mkpath(QStringLiteral("."));
}
if (dir.exists(name + QStringLiteral(".xml")))
if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", name + QStringLiteral(".xml"))) == KMessageBox::No) {
return;
}
QDomDocument doc;
QDomElement effect = m_effect.cloneNode().toElement();
doc.appendChild(doc.importNode(effect, true));
effect = doc.firstChild().toElement();
effect.removeAttribute(QStringLiteral("kdenlive_ix"));
effect.setAttribute(QStringLiteral("id"), name);
effect.setAttribute(QStringLiteral("type"), QStringLiteral("custom"));
/*
if (m_paramWidget) {
int in = m_paramWidget->range().x();
EffectsController::offsetKeyframes(in, effect);
}
*/
QDomElement effectname = effect.firstChildElement(QStringLiteral("name"));
effect.removeChild(effectname);
effectname = doc.createElement(QStringLiteral("name"));
QDomText nametext = doc.createTextNode(name);
effectname.appendChild(nametext);
effect.insertBefore(effectname, QDomNode());
QDomElement effectprops = effect.firstChildElement(QStringLiteral("properties"));
effectprops.setAttribute(QStringLiteral("id"), name);
effectprops.setAttribute(QStringLiteral("type"), QStringLiteral("custom"));
QFile file(dir.absoluteFilePath(name + QStringLiteral(".xml")));
if (file.open(QFile::WriteOnly | QFile::Truncate)) {
QTextStream out(&file);
out << doc.toString();
}
file.close();
emit reloadEffects();
}
void CollapsibleEffectView::slotResetEffect()
{
m_view->resetValues();
}
void CollapsibleEffectView::slotSwitch(bool expand)
{
slotShow(expand);
emit switchHeight(m_model, expand ? frame->height() + 4 : frame->height() + m_view->contentHeight() + 4);
setFixedHeight(expand ? frame->height() + 4 : frame->height() + m_view->contentHeight() + 4);
widgetFrame->setVisible(!expand);
/*if (!expand) {
widgetFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
widgetFrame->setFixedHeight(m_view->contentHeight());
} else {
widgetFrame->setFixedHeight(QWIDGETSIZE_MAX);
}*/
/*const QRect final_geometry = expand ? QRect(0, 0, width(), title->height()) : QRect(rect().topLeft(), size());
QPropertyAnimation *anim = new QPropertyAnimation(this, "geometry", this);
anim->setDuration(200);
anim->setEasingCurve(QEasingCurve::InOutQuad);
anim->setEndValue(final_geometry);
//connect(anim, SIGNAL(valueChanged(const QVariant &)), SLOT(animationChanged(const QVariant &)));
connect(anim, SIGNAL(finished()), SLOT(animationFinished()));
anim->start(QPropertyAnimation::DeleteWhenStopped);*/
}
void CollapsibleEffectView::animationChanged(const QVariant &geom)
{
parentWidget()->setFixedHeight(geom.toRect().height());
}
void CollapsibleEffectView::animationFinished()
{
if (m_collapse->isActive()) {
widgetFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored);
} else {
widgetFrame->setFixedHeight(m_view->contentHeight());
}
}
void CollapsibleEffectView::slotShow(bool show)
{
if (show) {
// collapseButton->setArrowType(Qt::DownArrow);
m_info.isCollapsed = false;
} else {
// collapseButton->setArrowType(Qt::RightArrow);
m_info.isCollapsed = true;
}
updateCollapsedState();
}
void CollapsibleEffectView::groupStateChanged(bool collapsed)
{
m_info.groupIsCollapsed = collapsed;
updateCollapsedState();
}
void CollapsibleEffectView::updateCollapsedState()
{
QString info = m_info.toString();
if (info != m_effect.attribute(QStringLiteral("kdenlive_info"))) {
m_effect.setAttribute(QStringLiteral("kdenlive_info"), info);
emit parameterChanged(m_original_effect, m_effect, effectIndex());
}
}
void CollapsibleEffectView::setGroupIndex(int ix)
{
if (m_info.groupIndex == -1 && ix != -1) {
m_menu->removeAction(m_groupAction);
} else if (m_info.groupIndex != -1 && ix == -1) {
m_menu->addAction(m_groupAction);
}
m_info.groupIndex = ix;
m_effect.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString());
}
void CollapsibleEffectView::setGroupName(const QString &groupName)
{
m_info.groupName = groupName;
m_effect.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString());
}
QString CollapsibleEffectView::infoString() const
{
return m_info.toString();
}
void CollapsibleEffectView::removeFromGroup()
{
if (m_info.groupIndex != -1) {
m_menu->addAction(m_groupAction);
}
m_info.groupIndex = -1;
m_info.groupName.clear();
m_effect.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString());
emit parameterChanged(m_original_effect, m_effect, effectIndex());
}
int CollapsibleEffectView::groupIndex() const
{
return m_info.groupIndex;
}
int CollapsibleEffectView::effectIndex() const
{
if (m_effect.isNull()) {
return -1;
}
return m_effect.attribute(QStringLiteral("kdenlive_ix")).toInt();
}
void CollapsibleEffectView::updateWidget(const ItemInfo &info, const QDomElement &effect)
{
// cleanup
/*
delete m_paramWidget;
m_paramWidget = nullptr;
*/
m_effect = effect;
setupWidget(info);
}
void CollapsibleEffectView::updateFrameInfo()
{
/*
if (m_paramWidget) {
m_paramWidget->refreshFrameInfo();
}
*/
}
void CollapsibleEffectView::setActiveKeyframe(int kf)
{
/*
if (m_paramWidget) {
m_paramWidget->setActiveKeyframe(kf);
}
*/
}
void CollapsibleEffectView::setupWidget(const ItemInfo &info)
{
/*
if (m_effect.isNull()) {
// //qCDebug(KDENLIVE_LOG) << "// EMPTY EFFECT STACK";
return;
}
delete m_paramWidget;
m_paramWidget = nullptr;
if (m_effect.attribute(QStringLiteral("tag")) == QLatin1String("region")) {
m_regionEffect = true;
QDomNodeList effects = m_effect.elementsByTagName(QStringLiteral("effect"));
QDomNodeList origin_effects = m_original_effect.elementsByTagName(QStringLiteral("effect"));
m_paramWidget = new ParameterContainer(m_effect, info, metaInfo, widgetFrame);
QWidget *container = new QWidget(widgetFrame);
QVBoxLayout *vbox = static_cast(widgetFrame->layout());
vbox->addWidget(container);
// m_paramWidget = new ParameterContainer(m_effect.toElement(), info, metaInfo, container);
for (int i = 0; i < effects.count(); ++i) {
bool canMoveUp = true;
if (i == 0 || effects.at(i - 1).toElement().attribute(QStringLiteral("id")) == QLatin1String("speed")) {
canMoveUp = false;
}
CollapsibleEffectView *coll = new CollapsibleEffectView(effects.at(i).toElement(), origin_effects.at(i).toElement(), info, metaInfo, canMoveUp,
i == effects.count() - 1, container);
m_subParamWidgets.append(coll);
connect(coll, &CollapsibleEffectView::parameterChanged, this, &CollapsibleEffectView::slotUpdateRegionEffectParams);
// container = new QWidget(widgetFrame);
vbox->addWidget(coll);
// p = new ParameterContainer(effects.at(i).toElement(), info, isEffect, container);
}
} else {
m_paramWidget = new ParameterContainer(m_effect, info, metaInfo, widgetFrame);
connect(m_paramWidget, &ParameterContainer::disableCurrentFilter, this, &CollapsibleEffectView::slotDisable);
connect(m_paramWidget, &ParameterContainer::importKeyframes, this, &CollapsibleEffectView::importKeyframes);
if (m_effect.firstChildElement(QStringLiteral("parameter")).isNull()) {
// Effect has no parameter, don't allow expand
collapseButton->setEnabled(false);
collapseButton->setVisible(false);
widgetFrame->setVisible(false);
}
}
if (collapseButton->isEnabled() && m_info.isCollapsed) {
widgetFrame->setVisible(false);
collapseButton->setArrowType(Qt::RightArrow);
}
connect(m_paramWidget, &ParameterContainer::parameterChanged, this, &CollapsibleEffectView::parameterChanged);
connect(m_paramWidget, &ParameterContainer::startFilterJob, this, &CollapsibleEffectView::startFilterJob);
connect(this, &CollapsibleEffectView::syncEffectsPos, m_paramWidget, &ParameterContainer::syncEffectsPos);
connect(m_paramWidget, &ParameterContainer::checkMonitorPosition, this, &CollapsibleEffectView::checkMonitorPosition);
connect(m_paramWidget, &ParameterContainer::seekTimeline, this, &CollapsibleEffectView::seekTimeline);
connect(m_paramWidget, &ParameterContainer::importClipKeyframes, this, &CollapsibleEffectView::prepareImportClipKeyframes);
*/
}
bool CollapsibleEffectView::isGroup() const
{
return false;
}
void CollapsibleEffectView::updateTimecodeFormat()
{
/*
m_paramWidget->updateTimecodeFormat();
if (!m_subParamWidgets.isEmpty()) {
// we have a group
for (int i = 0; i < m_subParamWidgets.count(); ++i) {
m_subParamWidgets.at(i)->updateTimecodeFormat();
}
}
*/
}
void CollapsibleEffectView::slotUpdateRegionEffectParams(const QDomElement & /*old*/, const QDomElement & /*e*/, int /*ix*/)
{
// qCDebug(KDENLIVE_LOG)<<"// EMIT CHANGE SUBEFFECT.....:";
emit parameterChanged(m_original_effect, m_effect, effectIndex());
}
void CollapsibleEffectView::slotSyncEffectsPos(int pos)
{
emit syncEffectsPos(pos);
}
void CollapsibleEffectView::dragEnterEvent(QDragEnterEvent *event)
{
/*
if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/effectslist"))) {
frame->setProperty("target", true);
frame->setStyleSheet(frame->styleSheet());
event->acceptProposedAction();
} else if (m_paramWidget->doesAcceptDrops() && event->mimeData()->hasFormat(QStringLiteral("kdenlive/geometry")) &&
event->source()->objectName() != QStringLiteral("ParameterContainer")) {
event->setDropAction(Qt::CopyAction);
event->setAccepted(true);
} else {
QWidget::dragEnterEvent(event);
}
*/
}
void CollapsibleEffectView::dragLeaveEvent(QDragLeaveEvent * /*event*/)
{
frame->setProperty("target", false);
frame->setStyleSheet(frame->styleSheet());
}
void CollapsibleEffectView::importKeyframes(const QString &kf)
{
QMap keyframes;
if (kf.contains(QLatin1Char('\n'))) {
const QStringList params = kf.split(QLatin1Char('\n'), QString::SkipEmptyParts);
for (const QString ¶m : params) {
keyframes.insert(param.section(QLatin1Char('='), 0, 0), param.section(QLatin1Char('='), 1));
}
} else {
keyframes.insert(kf.section(QLatin1Char('='), 0, 0), kf.section(QLatin1Char('='), 1));
}
emit importClipKeyframes(AVWidget, m_itemInfo, m_effect.cloneNode().toElement(), keyframes);
}
void CollapsibleEffectView::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/geometry"))) {
if (event->source()->objectName() == QStringLiteral("ParameterContainer")) {
return;
}
// emit activateEffect(effectIndex());
QString itemData = event->mimeData()->data(QStringLiteral("kdenlive/geometry"));
importKeyframes(itemData);
return;
}
frame->setProperty("target", false);
frame->setStyleSheet(frame->styleSheet());
const QString effects = QString::fromUtf8(event->mimeData()->data(QStringLiteral("kdenlive/effectslist")));
// event->acceptProposedAction();
QDomDocument doc;
doc.setContent(effects, true);
QDomElement e = doc.documentElement();
int ix = e.attribute(QStringLiteral("kdenlive_ix")).toInt();
int currentEffectIx = effectIndex();
if (ix == currentEffectIx || e.attribute(QStringLiteral("id")) == QLatin1String("speed")) {
// effect dropped on itself, or unmovable speed dropped, reject
event->ignore();
return;
}
if (ix == 0 || e.tagName() == QLatin1String("effectgroup")) {
if (e.tagName() == QLatin1String("effectgroup")) {
// moving a group
QDomNodeList subeffects = e.elementsByTagName(QStringLiteral("effect"));
if (subeffects.isEmpty()) {
event->ignore();
return;
}
EffectInfo info;
info.fromString(subeffects.at(0).toElement().attribute(QStringLiteral("kdenlive_info")));
event->setDropAction(Qt::MoveAction);
event->accept();
if (info.groupIndex >= 0) {
// Moving group
QList effectsIds;
// Collect moved effects ids
for (int i = 0; i < subeffects.count(); ++i) {
QDomElement effect = subeffects.at(i).toElement();
effectsIds << effect.attribute(QStringLiteral("kdenlive_ix")).toInt();
}
// emit moveEffect(effectsIds, currentEffectIx, info.groupIndex, info.groupName);
} else {
// group effect dropped from effect list
if (m_info.groupIndex > -1) {
// TODO: Should we merge groups??
}
emit addEffect(e);
}
return;
}
// effect dropped from effects list, add it
e.setAttribute(QStringLiteral("kdenlive_ix"), ix);
if (m_info.groupIndex > -1) {
// Dropped on a group
e.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString());
}
event->setDropAction(Qt::CopyAction);
event->accept();
emit addEffect(e);
return;
}
// emit moveEffect(QList() << ix, currentEffectIx, m_info.groupIndex, m_info.groupName);
event->setDropAction(Qt::MoveAction);
event->accept();
}
void CollapsibleEffectView::adjustButtons(int ix, int max)
{
buttonUp->setEnabled(ix > 0);
buttonDown->setEnabled(ix < max - 1);
}
MonitorSceneType CollapsibleEffectView::needsMonitorEffectScene() const
{
/*
if ((m_paramWidget != nullptr) && !m_enabledButton->isActive()) {
return m_paramWidget->needsMonitorEffectScene();
}
*/
return MonitorSceneDefault;
}
void CollapsibleEffectView::setRange(QPair range)
{
if (m_view) {
m_view->setRange(range);
}
}
void CollapsibleEffectView::setKeyframes(const QString &tag, const QString &keyframes)
{
/*
m_paramWidget->setKeyframes(tag, keyframes);
*/
}
bool CollapsibleEffectView::isMovable() const
{
return m_isMovable;
}
void CollapsibleEffectView::prepareImportClipKeyframes()
{
emit importClipKeyframes(AVWidget, m_itemInfo, m_effect.cloneNode().toElement(), QMap());
}
diff --git a/tests/keyframetest.cpp b/tests/keyframetest.cpp
index 657c886d7..f1691ba4a 100644
--- a/tests/keyframetest.cpp
+++ b/tests/keyframetest.cpp
@@ -1,218 +1,219 @@
#include "test_utils.hpp"
using namespace fakeit;
bool test_model_equality(std::shared_ptr m1, std::shared_ptr m2)
{
// we cheat a bit by simply comparing the underlying map
qDebug() << "Equality test"<m_keyframeList.size()<m_keyframeList.size();
return m1->m_keyframeList == m2->m_keyframeList;
}
bool check_anim_identity(std::shared_ptr m)
{
auto m2 = std::shared_ptr(new KeyframeModel(m->m_model, m->m_index, m->m_undoStack));
m2->parseAnimProperty(m->getAnimProperty());
return test_model_equality(m, m2);
}
TEST_CASE("Keyframe model", "[KeyframeModel]")
{
std::shared_ptr undoStack = std::make_shared(nullptr);
std::shared_ptr guideModel = std::make_shared(undoStack);
// Here we do some trickery to enable testing.
// We mock the project class so that the undoStack function returns our undoStack
Mock pmMock;
When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
ProjectManager &mocked = pmMock.get();
pCore->m_projectManager = &mocked;
Mlt::Profile pr;
std::shared_ptr producer = std::make_shared(pr, "color", "red");
auto effectstack = EffectStackModel::construct(producer, {ObjectType::TimelineClip, 0}, undoStack);
effectstack->appendEffect(QStringLiteral("audiobalance"));
REQUIRE(effectstack->checkConsistency());
REQUIRE(effectstack->rowCount() == 1);
auto effect = std::dynamic_pointer_cast(effectstack->getEffectStackRow(0));
+ effect->prepareKeyframes();
qDebug() << effect->getAssetId() << effect->getAllParameters();
REQUIRE(effect->rowCount() == 1);
QModelIndex index = effect->index(0, 0);
auto model = std::shared_ptr(new KeyframeModel(effect, index, undoStack));
SECTION("Add/remove + undo")
{
auto state0 = [&]() {
REQUIRE(model->rowCount() == 1);
REQUIRE(check_anim_identity(model));
};
state0();
REQUIRE(model->addKeyframe(GenTime(1.1), KeyframeType::Linear, 42));
auto state1 = [&]() {
REQUIRE(model->rowCount() == 2);
REQUIRE(check_anim_identity(model));
REQUIRE(model->hasKeyframe(GenTime(1.1)));
bool ok;
auto k = model->getKeyframe(GenTime(1.1), &ok);
REQUIRE(ok);
auto k0 = model->getKeyframe(GenTime(0), &ok);
REQUIRE(ok);
auto k1 = model->getClosestKeyframe(GenTime(0.655555), &ok);
REQUIRE(ok);
REQUIRE(k == k1);
auto k2 = model->getNextKeyframe(GenTime(0.5), &ok);
REQUIRE(ok);
REQUIRE(k == k2);
auto k3 = model->getPrevKeyframe(GenTime(0.5), &ok);
REQUIRE(ok);
REQUIRE(k3 == k0);
auto k4 = model->getPrevKeyframe(GenTime(10), &ok);
REQUIRE(ok);
REQUIRE(k == k4);
model->getNextKeyframe(GenTime(10), &ok);
REQUIRE_FALSE(ok);
};
state1();
undoStack->undo(); state0();
undoStack->redo(); state1();
REQUIRE(model->addKeyframe(GenTime(12.6), KeyframeType::Discrete, 33));
auto state2 = [&]() {
REQUIRE(model->rowCount() == 3);
REQUIRE(check_anim_identity(model));
REQUIRE(model->hasKeyframe(GenTime(1.1)));
REQUIRE(model->hasKeyframe(GenTime(12.6)));
bool ok;
auto k = model->getKeyframe(GenTime(1.1), &ok);
REQUIRE(ok);
auto k0 = model->getKeyframe(GenTime(0), &ok);
REQUIRE(ok);
auto kk = model->getKeyframe(GenTime(12.6), &ok);
REQUIRE(ok);
auto k1 = model->getClosestKeyframe(GenTime(0.655555), &ok);
REQUIRE(ok);
REQUIRE(k == k1);
auto k2 = model->getNextKeyframe(GenTime(0.5), &ok);
REQUIRE(ok);
REQUIRE(k == k2);
auto k3 = model->getPrevKeyframe(GenTime(0.5), &ok);
REQUIRE(ok);
REQUIRE(k3 == k0);
auto k4 = model->getPrevKeyframe(GenTime(10), &ok);
REQUIRE(ok);
REQUIRE(k == k4);
auto k5 = model->getNextKeyframe(GenTime(10), &ok);
REQUIRE(ok);
REQUIRE(k5 == kk);
};
state2();
undoStack->undo(); state1();
undoStack->undo(); state0();
undoStack->redo(); state1();
undoStack->redo(); state2();
REQUIRE(model->removeKeyframe(GenTime(1.1)));
auto state3 = [&]() {
REQUIRE(model->rowCount() == 2);
REQUIRE(check_anim_identity(model));
REQUIRE(model->hasKeyframe(GenTime(12.6)));
bool ok;
auto k = model->getKeyframe(GenTime(1.1), &ok);
REQUIRE_FALSE(ok);
auto k0 = model->getKeyframe(GenTime(0), &ok);
REQUIRE(ok);
auto kk = model->getKeyframe(GenTime(12.6), &ok);
REQUIRE(ok);
auto k1 = model->getClosestKeyframe(GenTime(0.655555), &ok);
REQUIRE(ok);
REQUIRE(k == k0);
auto k2 = model->getNextKeyframe(GenTime(0.5), &ok);
REQUIRE(ok);
REQUIRE(kk == k2);
auto k3 = model->getPrevKeyframe(GenTime(0.5), &ok);
REQUIRE(ok);
REQUIRE(k3 == k0);
auto k4 = model->getPrevKeyframe(GenTime(10), &ok);
REQUIRE(ok);
REQUIRE(k0 == k4);
auto k5 = model->getNextKeyframe(GenTime(10), &ok);
REQUIRE(ok);
REQUIRE(k5 == kk);
};
state3();
undoStack->undo(); state2();
undoStack->undo(); state1();
undoStack->undo(); state0();
undoStack->redo(); state1();
undoStack->redo(); state2();
undoStack->redo(); state3();
REQUIRE(model->removeAllKeyframes());
state0();
undoStack->undo(); state3();
undoStack->redo(); state0();
}
SECTION("Move keyframes + undo")
{
auto state0 = [&]() {
REQUIRE(model->rowCount() == 1);
REQUIRE(check_anim_identity(model));
};
state0();
REQUIRE(model->addKeyframe(GenTime(1.1), KeyframeType::Linear, 42));
auto state1 = [&](double pos) {
REQUIRE(model->rowCount() == 2);
REQUIRE(check_anim_identity(model));
REQUIRE(model->hasKeyframe(GenTime(pos)));
bool ok;
auto k = model->getKeyframe(GenTime(pos), &ok);
REQUIRE(ok);
auto k0 = model->getKeyframe(GenTime(0), &ok);
REQUIRE(ok);
auto k1 = model->getClosestKeyframe(GenTime(pos + 10), &ok);
REQUIRE(ok);
REQUIRE(k == k1);
auto k2 = model->getNextKeyframe(GenTime(pos - 0.3), &ok);
REQUIRE(ok);
REQUIRE(k == k2);
auto k3 = model->getPrevKeyframe(GenTime(pos - 0.3), &ok);
REQUIRE(ok);
REQUIRE(k3 == k0);
auto k4 = model->getPrevKeyframe(GenTime(pos + 0.3), &ok);
REQUIRE(ok);
REQUIRE(k == k4);
model->getNextKeyframe(GenTime(pos + 0.3), &ok);
REQUIRE_FALSE(ok);
};
state1(1.1);
REQUIRE(model->moveKeyframe(GenTime(1.1), GenTime(2.6), true));
state1(2.6);
undoStack->undo(); state1(1.1);
undoStack->redo(); state1(2.6);
REQUIRE(model->moveKeyframe(GenTime(2.6), GenTime(6.1), true));
state1(6.1);
undoStack->undo(); state1(2.6);
undoStack->undo(); state1(1.1);
undoStack->redo(); state1(2.6);
undoStack->redo(); state1(6.1);
REQUIRE(model->addKeyframe(GenTime(12.6), KeyframeType::Discrete, 33));
REQUIRE_FALSE(model->moveKeyframe(GenTime(6.1), GenTime(12.6), true));
undoStack->undo(); state1(6.1);
}
}