diff --git a/src/assets/model/assetparametermodel.cpp b/src/assets/model/assetparametermodel.cpp index 2b2560804..1534dd493 100644 --- a/src/assets/model/assetparametermodel.cpp +++ b/src/assets/model/assetparametermodel.cpp @@ -1,853 +1,853 @@ /*************************************************************************** * 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 #include #include AssetParameterModel::AssetParameterModel(std::unique_ptr asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId, QObject *parent) : QAbstractListModel(parent) , monitorId(ownerId.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor) , m_assetId(assetId) , m_ownerId(ownerId) , m_asset(std::move(asset)) , m_keyframes(nullptr) { Q_ASSERT(m_asset->is_valid()); QDomNodeList nodeList = assetXml.elementsByTagName(QStringLiteral("parameter")); m_hideKeyframesByDefault = assetXml.hasAttribute(QStringLiteral("hideKeyframes")); bool needsLocaleConversion = false; QChar separator, oldSeparator; // Check locale, default effects xml has no LC_NUMERIC defined and always uses the C locale QLocale locale; if (assetXml.hasAttribute(QStringLiteral("LC_NUMERIC"))) { QLocale effectLocale = QLocale(assetXml.attribute(QStringLiteral("LC_NUMERIC"))); if (QLocale::c().decimalPoint() != effectLocale.decimalPoint()) { needsLocaleConversion = true; separator = QLocale::c().decimalPoint(); oldSeparator = effectLocale.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")); ParamRow currentRow; currentRow.type = paramTypeFromStr(type); currentRow.xml = currentParameter; - if (value.isNull()) { + if (value.isEmpty()) { QVariant defaultValue = parseAttribute(m_ownerId, QStringLiteral("default"), currentParameter); value = defaultValue.type() == QVariant::Double ? locale.toString(defaultValue.toDouble()) : defaultValue.toString(); } bool isFixed = (type == QLatin1String("fixed")); if (isFixed) { m_fixedParams[name] = value; } else if (currentRow.type == ParamType::Position) { int val = value.toInt(); if (val < 0) { int in = pCore->getItemIn(m_ownerId); int out = in + pCore->getItemDuration(m_ownerId); val += out; value = QString::number(val); } } else if (currentRow.type == ParamType::KeyframeParam || currentRow.type == ParamType::AnimatedRect) { if (!value.contains(QLatin1Char('='))) { value.prepend(QStringLiteral("%1=").arg(pCore->getItemIn(m_ownerId))); } } if (!name.isEmpty()) { internalSetParameter(name, value); // Keep track of param order m_paramOrder.push_back(name); } if (isFixed) { // fixed parameters are not displayed so we don't store them. continue; } currentRow.value = value; QString title = currentParameter.firstChildElement(QStringLiteral("name")).text(); currentRow.name = title.isEmpty() ? name : title; m_params[name] = currentRow; m_rows.push_back(name); } if (m_assetId.startsWith(QStringLiteral("sox_"))) { // Sox effects need to have a special "Effect" value set QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)}; for (const QString &pName : m_paramOrder) { effectParam << m_asset->get(pName.toUtf8().constData()); } m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData()); } qDebug() << "END parsing of " << assetId << ". Number of found parameters" << m_rows.size(); emit modelChanged(); } void AssetParameterModel::prepareKeyframes() { if (m_keyframes) return; int ix = 0; for (const auto &name : m_rows) { if (m_params.at(name).type == ParamType::KeyframeParam || m_params.at(name).type == ParamType::AnimatedRect || m_params.at(name).type == ParamType::Roto_spline) { addKeyframeParam(index(ix, 0)); } ix++; } if (m_keyframes) { // Make sure we have keyframes at same position for all parameters m_keyframes->checkConsistency(); } } QStringList AssetParameterModel::getKeyframableParameters() const { QStringList paramNames; int ix = 0; for (const auto &name : m_rows) { if (m_params.at(name).type == ParamType::KeyframeParam || m_params.at(name).type == ParamType::AnimatedRect) { //addKeyframeParam(index(ix, 0)); paramNames << name; } ix++; } return paramNames; } void AssetParameterModel::setParameter(const QString &name, int value, bool update) { 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; } if (m_assetId.startsWith(QStringLiteral("sox_"))) { // Warning, SOX effect, need unplug/replug qDebug() << "// Warning, SOX effect, need unplug/replug"; QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)}; for (const QString &pName : m_paramOrder) { effectParam << m_asset->get(pName.toUtf8().constData()); } m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData()); emit replugEffect(shared_from_this()); } else if (m_assetId == QLatin1String("autotrack_rectangle") || m_assetId.startsWith(QStringLiteral("ladspa"))) { // these effects don't understand param change and need to be rebuild emit replugEffect(shared_from_this()); } if (update) { emit modelChanged(); emit dataChanged(index(0, 0), index(m_rows.count() - 1, 0), {}); // Update fades in timeline pCore->updateItemModel(m_ownerId, m_assetId); // Trigger monitor refresh pCore->refreshProjectItem(m_ownerId); // Invalidate timeline preview pCore->invalidateItem(m_ownerId); } } void AssetParameterModel::internalSetParameter(const QString &name, const QString ¶mValue, const QModelIndex ¶mIndex) { Q_ASSERT(m_asset->is_valid()); QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); // TODO: this does not really belong here, but I don't see another way to do it so that undo works if (data(paramIndex, AssetParameterModel::TypeRole).value() == ParamType::Curve) { QStringList vals = paramValue.split(QLatin1Char(';'), QString::SkipEmptyParts); int points = vals.size(); m_asset->set("3", points / 10.); // for the curve, inpoints are numbered: 6, 8, 10, 12, 14 // outpoints, 7, 9, 11, 13,15 so we need to deduce these enums for (int i = 0; i < points; i++) { const QString &pointVal = vals.at(i); int idx = 2 * i + 6; m_asset->set(QString::number(idx).toLatin1().constData(), pointVal.section(QLatin1Char('/'), 0, 0).toDouble()); idx++; m_asset->set(QString::number(idx).toLatin1().constData(), pointVal.section(QLatin1Char('/'), 1, 1).toDouble()); } } bool conversionSuccess; double doubleValue = locale.toDouble(paramValue, &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(), paramValue.toUtf8().constData()); qDebug() << " = = SET EFFECT PARAM: " << name << " = " << paramValue; if (m_fixedParams.count(name) == 0) { m_params[name].value = paramValue; if (m_keyframes) { KeyframeModel *km = m_keyframes->getKeyModel(paramIndex); if (km) { km->refresh(); } //m_keyframes->refresh(); } } else { m_fixedParams[name] = paramValue; } } } void AssetParameterModel::setParameter(const QString &name, const QString ¶mValue, bool update, const QModelIndex ¶mIndex) { //qDebug() << "// PROCESSING PARAM CHANGE: " << name << ", UPDATE: " << update << ", VAL: " << paramValue; internalSetParameter(name, paramValue, paramIndex); bool updateChildRequired = true; if (m_assetId.startsWith(QStringLiteral("sox_"))) { // Warning, SOX effect, need unplug/replug QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)}; for (const QString &pName : m_paramOrder) { effectParam << m_asset->get(pName.toUtf8().constData()); } m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData()); emit replugEffect(shared_from_this()); updateChildRequired = false; } else if (m_assetId == QLatin1String("autotrack_rectangle") || m_assetId.startsWith(QStringLiteral("ladspa"))) { // these effects don't understand param change and need to be rebuild emit replugEffect(shared_from_this()); updateChildRequired = false; } else if (update) { qDebug() << "// SENDING DATA CHANGE...."; if (paramIndex.isValid()) { emit dataChanged(paramIndex, paramIndex); } else { QModelIndex ix = index(m_rows.indexOf(name), 0); emit dataChanged(ix, ix); } emit modelChanged(); } if (updateChildRequired) { emit updateChildren(name); } // Update timeline view if necessary if (m_ownerId.first == ObjectType::NoItem) { // Used for generator clips if (!update) emit modelChanged(); } else { // Update fades in timeline pCore->updateItemModel(m_ownerId, m_assetId); // Trigger monitor refresh pCore->refreshProjectItem(m_ownerId); // Invalidate timeline preview 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 ParentPositionRole: return pCore->getItemPosition(m_ownerId); case HideKeyframesFirstRole: return m_hideKeyframesByDefault; case MinRole: return parseAttribute(m_ownerId, QStringLiteral("min"), element); case MaxRole: return parseAttribute(m_ownerId, QStringLiteral("max"), element); case FactorRole: return parseAttribute(m_ownerId, QStringLiteral("factor"), element, 1); case ScaleRole: return parseAttribute(m_ownerId, QStringLiteral("scale"), element, 0); case DecimalsRole: return parseAttribute(m_ownerId, QStringLiteral("decimals"), element); case DefaultRole: return parseAttribute(m_ownerId, QStringLiteral("default"), element); case FilterRole: return parseAttribute(m_ownerId, QStringLiteral("filter"), element); case FilterParamsRole: return parseAttribute(m_ownerId, QStringLiteral("filterparams"), element); case FilterJobParamsRole: return parseSubAttributes(QStringLiteral("jobparam"), element); case AlternateNameRole: { QDomNode child = element.firstChildElement(QStringLiteral("name")); if (child.toElement().hasAttribute(QStringLiteral("conditional"))) { return child.toElement().attribute(QStringLiteral("conditional")); } return m_params.at(paramName).name; } case SuffixRole: return element.attribute(QStringLiteral("suffix")); case OpacityRole: return element.attribute(QStringLiteral("opacity")) != QLatin1String("false"); case RelativePosRole: return element.attribute(QStringLiteral("relative")) == QLatin1String("true"); case ShowInTimelineRole: return !element.hasAttribute(QStringLiteral("notintimeline")); case AlphaRole: return element.attribute(QStringLiteral("alpha")) == QLatin1String("1"); case ValueRole: { QString value(m_asset->get(paramName.toUtf8().constData())); return value.isEmpty() ? (element.attribute(QStringLiteral("value")).isNull() ? parseAttribute(m_ownerId, 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(',')); } case List1Role: return parseAttribute(m_ownerId, QStringLiteral("list1"), element); case List2Role: return parseAttribute(m_ownerId, QStringLiteral("list2"), element); case Enum1Role: return m_asset->get_double("1"); case Enum2Role: return m_asset->get_double("2"); case Enum3Role: return m_asset->get_double("3"); case Enum4Role: return m_asset->get_double("4"); case Enum5Role: return m_asset->get_double("5"); case Enum6Role: return m_asset->get_double("6"); case Enum7Role: return m_asset->get_double("7"); case Enum8Role: return m_asset->get_double("8"); case Enum9Role: return m_asset->get_double("9"); case Enum10Role: return m_asset->get_double("10"); case Enum11Role: return m_asset->get_double("11"); case Enum12Role: return m_asset->get_double("12"); case Enum13Role: return m_asset->get_double("13"); case Enum14Role: return m_asset->get_double("14"); case Enum15Role: return m_asset->get_double("15"); } 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::KeyframeParam; } 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") || type == QLatin1String("animated")) { return ParamType::KeyframeParam; } else if (type == QLatin1String("color")) { return ParamType::Color; } else if (type == QLatin1String("colorwheel")) { return ParamType::ColorWheel; } 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; } else if (type == QLatin1String("hidden")) { return ParamType::Hidden; } 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 ObjectId &owner, 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(); int in = pCore->getItemIn(owner); int out = in + pCore->getItemDuration(owner); // 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)) .replace(QLatin1String("%out"), QString::number(out)); if (type == ParamType::Double || type == ParamType::Hidden) { // Use a Mlt::Properties to parse mathematical operators Mlt::Properties p; p.set("eval", content.prepend(QLatin1Char('@')).toLatin1().constData()); return p.get_double("eval"); } } else if (type == ParamType::Double || type == ParamType::Hidden) { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); return locale.toDouble(content); } if (attribute == QLatin1String("default")) { if (type == ParamType::RestrictedAnim) { content = getDefaultKeyframes(0, content, true); } else if (type == ParamType::KeyframeParam) { return content.toDouble(); } else if (type == ParamType::List) { bool ok; double res = content.toDouble(&ok); if (ok) { return res; } - return defaultValue; + return defaultValue.isNull() ? content : defaultValue; } else if (type == ParamType::Bezier_spline) { QLocale locale; if (locale.decimalPoint() != QLocale::c().decimalPoint()) { return content.replace(QLocale::c().decimalPoint(), locale.decimalPoint()); } } } return content; } QVariant AssetParameterModel::parseSubAttributes(const QString &attribute, const QDomElement &element) const { QDomNodeList nodeList = element.elementsByTagName(attribute); if (nodeList.isEmpty()) { return QVariant(); } QVariantList jobDataList; for (int i = 0; i < nodeList.count(); ++i) { QDomElement currentParameter = nodeList.item(i).toElement(); QStringList jobData {currentParameter.attribute(QStringLiteral("name")), currentParameter.text()}; jobDataList << jobData; } return jobDataList; } 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; } QJsonDocument AssetParameterModel::toJson(bool includeFixed) const { QJsonArray list; QLocale locale; if (includeFixed) { for (const auto &fixed : m_fixedParams) { QJsonObject currentParam; QModelIndex ix = index(m_rows.indexOf(fixed.first), 0); currentParam.insert(QLatin1String("name"), QJsonValue(fixed.first)); currentParam.insert(QLatin1String("value"), fixed.second.toString()); int type = data(ix, AssetParameterModel::TypeRole).toInt(); double min = data(ix, AssetParameterModel::MinRole).toDouble(); double max = data(ix, AssetParameterModel::MaxRole).toDouble(); double factor = data(ix, AssetParameterModel::FactorRole).toDouble(); int in = data(ix, AssetParameterModel::ParentInRole).toInt(); int out = in + data(ix, AssetParameterModel::ParentDurationRole).toInt(); if (factor > 0) { min /= factor; max /= factor; } currentParam.insert(QLatin1String("type"), QJsonValue(type)); currentParam.insert(QLatin1String("min"), QJsonValue(min)); currentParam.insert(QLatin1String("max"), QJsonValue(max)); currentParam.insert(QLatin1String("in"), QJsonValue(in)); currentParam.insert(QLatin1String("out"), QJsonValue(out)); list.push_back(currentParam); } } for (const auto ¶m : m_params) { if (!includeFixed && param.second.type != ParamType::KeyframeParam && param.second.type != ParamType::AnimatedRect) { continue; } QJsonObject currentParam; QModelIndex ix = index(m_rows.indexOf(param.first), 0); currentParam.insert(QLatin1String("name"), QJsonValue(param.first)); currentParam.insert(QLatin1String("value"), QJsonValue(param.second.value.toString())); int type = data(ix, AssetParameterModel::TypeRole).toInt(); double min = data(ix, AssetParameterModel::MinRole).toDouble(); double max = data(ix, AssetParameterModel::MaxRole).toDouble(); double factor = data(ix, AssetParameterModel::FactorRole).toDouble(); int in = data(ix, AssetParameterModel::ParentInRole).toInt(); int out = in + data(ix, AssetParameterModel::ParentDurationRole).toInt(); if (factor > 0) { min /= factor; max /= factor; } currentParam.insert(QLatin1String("type"), QJsonValue(type)); currentParam.insert(QLatin1String("min"), QJsonValue(min)); currentParam.insert(QLatin1String("max"), QJsonValue(max)); currentParam.insert(QLatin1String("in"), QJsonValue(in)); currentParam.insert(QLatin1String("out"), QJsonValue(out)); list.push_back(currentParam); } return QJsonDocument(list); } void AssetParameterModel::deletePreset(const QString &presetFile, const QString &presetName) { QJsonObject object; QJsonArray array; QFile loadFile(presetFile); if (loadFile.exists()) { if (loadFile.open(QIODevice::ReadOnly)) { QByteArray saveData = loadFile.readAll(); QJsonDocument loadDoc(QJsonDocument::fromJson(saveData)); if (loadDoc.isArray()) { qDebug() << " * * ** JSON IS AN ARRAY, DELETING: " << presetName; array = loadDoc.array(); QList toDelete; for (int i = 0; i < array.size(); i++) { QJsonValue val = array.at(i); if (val.isObject() && val.toObject().keys().contains(presetName)) { toDelete << i; } } for (int i : toDelete) { array.removeAt(i); } } else if (loadDoc.isObject()) { QJsonObject obj = loadDoc.object(); qDebug() << " * * ** JSON IS AN OBJECT, DELETING: " << presetName; if (obj.keys().contains(presetName)) { obj.remove(presetName); } else { qDebug() << " * * ** JSON DOES NOT CONTAIN: " << obj.keys(); } array.append(obj); } loadFile.close(); } else if (!loadFile.open(QIODevice::ReadWrite)) { // TODO: error message } } if (!loadFile.open(QIODevice::WriteOnly)) { // TODO: error message } loadFile.write(QJsonDocument(array).toJson()); } void AssetParameterModel::savePreset(const QString &presetFile, const QString &presetName) { QJsonObject object; QJsonArray array; QJsonDocument doc = toJson(); QFile loadFile(presetFile); if (loadFile.exists()) { if (loadFile.open(QIODevice::ReadOnly)) { QByteArray saveData = loadFile.readAll(); QJsonDocument loadDoc(QJsonDocument::fromJson(saveData)); if (loadDoc.isArray()) { array = loadDoc.array(); QList toDelete; for (int i = 0; i < array.size(); i++) { QJsonValue val = array.at(i); if (val.isObject() && val.toObject().keys().contains(presetName)) { toDelete << i; } } for (int i : toDelete) { array.removeAt(i); } } else if (loadDoc.isObject()) { QJsonObject obj = loadDoc.object(); if (obj.keys().contains(presetName)) { obj.remove(presetName); } array.append(obj); } loadFile.close(); } else if (!loadFile.open(QIODevice::ReadWrite)) { // TODO: error message } } if (!loadFile.open(QIODevice::WriteOnly)) { // TODO: error message } object[presetName] = doc.array(); array.append(object); loadFile.write(QJsonDocument(array).toJson()); } const QStringList AssetParameterModel::getPresetList(const QString &presetFile) const { QFile loadFile(presetFile); if (loadFile.exists() && loadFile.open(QIODevice::ReadOnly)) { QByteArray saveData = loadFile.readAll(); QJsonDocument loadDoc(QJsonDocument::fromJson(saveData)); if (loadDoc.isObject()) { qDebug() << "// PRESET LIST IS AN OBJECT!!!"; return loadDoc.object().keys(); } else if (loadDoc.isArray()) { qDebug() << "// PRESET LIST IS AN ARRAY!!!"; QStringList result; QJsonArray array = loadDoc.array(); for (auto &&i : array) { QJsonValue val = i; if (val.isObject()) { result << val.toObject().keys(); } } return result; } } return QStringList(); } const QVector> AssetParameterModel::loadPreset(const QString &presetFile, const QString &presetName) { QFile loadFile(presetFile); QVector> params; if (loadFile.exists() && loadFile.open(QIODevice::ReadOnly)) { QByteArray saveData = loadFile.readAll(); QJsonDocument loadDoc(QJsonDocument::fromJson(saveData)); if (loadDoc.isObject() && loadDoc.object().contains(presetName)) { qDebug() << "..........\n..........\nLOADING OBJECT JSON"; QJsonValue val = loadDoc.object().value(presetName); if (val.isObject()) { QVariantMap map = val.toObject().toVariantMap(); QMap::const_iterator i = map.constBegin(); while (i != map.constEnd()) { params.append({i.key(), i.value()}); ++i; } } } else if (loadDoc.isArray()) { QJsonArray array = loadDoc.array(); for (auto &&i : array) { QJsonValue val = i; if (val.isObject() && val.toObject().contains(presetName)) { QJsonValue preset = val.toObject().value(presetName); if (preset.isArray()) { QJsonArray paramArray = preset.toArray(); for (auto &&j : paramArray) { QJsonValue v1 = j; if (v1.isObject()) { QJsonObject ob = v1.toObject(); params.append({ob.value("name").toString(), ob.value("value").toVariant()}); } } } qDebug() << "// LOADED PRESET: " << presetName << "\n" << params; break; } } } } return params; } void AssetParameterModel::setParameters(const QVector> ¶ms) { QLocale locale; for (const auto ¶m : params) { if (param.second.type() == QVariant::Double) { setParameter(param.first, locale.toString(param.second.toDouble()), false); } else { setParameter(param.first, param.second.toString(), false); } } if (m_keyframes) { m_keyframes->refresh(); } emit dataChanged(index(0), index(m_rows.count()), {}); } 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; } void AssetParameterModel::resetAsset(std::unique_ptr asset) { m_asset = std::move(asset); } bool AssetParameterModel::hasMoreThanOneKeyframe() const { if (m_keyframes) { return (!m_keyframes->isEmpty() && !m_keyframes->singleKeyframe()); } return false; } int AssetParameterModel::time_to_frames(const QString &time) { return m_asset->time_to_frames(time.toUtf8().constData()); } void AssetParameterModel::passProperties(Mlt::Properties &target) { target.set("_profile", pCore->getCurrentProfile()->get_profile(), 0); target.set_lcnumeric(m_asset->get_lcnumeric()); } diff --git a/src/assets/view/widgets/keyframewidget.cpp b/src/assets/view/widgets/keyframewidget.cpp index 6b9554912..31f87cbf9 100644 --- a/src/assets/view/widgets/keyframewidget.cpp +++ b/src/assets/view/widgets/keyframewidget.cpp @@ -1,489 +1,491 @@ /*************************************************************************** * 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/model/corners/cornershelper.hpp" #include "assets/keyframes/model/keyframemodellist.hpp" #include "assets/keyframes/model/rotoscoping/rotohelper.hpp" #include "assets/keyframes/view/keyframeview.hpp" #include "assets/model/assetparametermodel.hpp" #include "assets/view/widgets/keyframeimport.h" #include "core.h" #include "kdenlivesettings.h" #include "monitor/monitor.h" #include "timecode.h" #include "timecodedisplay.h" #include "widgets/doublewidget.h" #include "widgets/geometrywidget.h" #include #include #include #include #include #include #include #include #include #include #include #include KeyframeWidget::KeyframeWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) , m_monitorHelper(nullptr) , m_neededScene(MonitorSceneType::MonitorSceneDefault) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); m_lay = new QVBoxLayout(this); m_lay->setContentsMargins(2, 2, 2, 0); m_lay->setSpacing(0); bool ok = false; int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); Q_ASSERT(ok); m_model->prepareKeyframes(); m_keyframes = m_model->getKeyframeModel(); m_keyframeview = new KeyframeView(m_keyframes, duration, this); m_buttonAddDelete = new QToolButton(this); m_buttonAddDelete->setAutoRaise(true); m_buttonAddDelete->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_buttonAddDelete->setToolTip(i18n("Add keyframe")); m_buttonPrevious = new QToolButton(this); m_buttonPrevious->setAutoRaise(true); m_buttonPrevious->setIcon(QIcon::fromTheme(QStringLiteral("media-skip-backward"))); m_buttonPrevious->setToolTip(i18n("Go to previous keyframe")); m_buttonNext = new QToolButton(this); m_buttonNext->setAutoRaise(true); m_buttonNext->setIcon(QIcon::fromTheme(QStringLiteral("media-skip-forward"))); m_buttonNext->setToolTip(i18n("Go to next keyframe")); // Keyframe type widget m_selectType = new KSelectAction(QIcon::fromTheme(QStringLiteral("keyframes")), i18n("Keyframe interpolation"), this); QAction *linear = new QAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Linear"), this); linear->setData((int)mlt_keyframe_linear); linear->setCheckable(true); m_selectType->addAction(linear); QAction *discrete = new QAction(QIcon::fromTheme(QStringLiteral("discrete")), i18n("Discrete"), this); discrete->setData((int)mlt_keyframe_discrete); discrete->setCheckable(true); m_selectType->addAction(discrete); QAction *curve = new QAction(QIcon::fromTheme(QStringLiteral("smooth")), i18n("Smooth"), this); curve->setData((int)mlt_keyframe_smooth); curve->setCheckable(true); m_selectType->addAction(curve); m_selectType->setCurrentAction(linear); connect(m_selectType, static_cast(&KSelectAction::triggered), this, &KeyframeWidget::slotEditKeyframeType); m_selectType->setToolBarMode(KSelectAction::ComboBoxMode); m_toolbar = new QToolBar(this); Monitor *monitor = pCore->getMonitor(m_model->monitorId); m_time = new TimecodeDisplay(monitor->timecode(), this); m_time->setRange(0, duration - 1); m_toolbar->addWidget(m_buttonPrevious); m_toolbar->addWidget(m_buttonAddDelete); m_toolbar->addWidget(m_buttonNext); m_toolbar->addAction(m_selectType); // copy/paste keyframes from clipboard QAction *copy = new QAction(i18n("Copy keyframes to clipboard"), this); connect(copy, &QAction::triggered, this, &KeyframeWidget::slotCopyKeyframes); QAction *paste = new QAction(i18n("Import keyframes from clipboard"), this); connect(paste, &QAction::triggered, this, &KeyframeWidget::slotImportKeyframes); // Remove keyframes QAction *removeNext = new QAction(i18n("Remove all keyframes after cursor"), this); connect(removeNext, &QAction::triggered, this, &KeyframeWidget::slotRemoveNextKeyframes); // Default kf interpolation KSelectAction *kfType = new KSelectAction(i18n("Default keyframe type"), this); QAction *discrete2 = new QAction(QIcon::fromTheme(QStringLiteral("discrete")), i18n("Discrete"), this); discrete2->setData((int)mlt_keyframe_discrete); discrete2->setCheckable(true); kfType->addAction(discrete2); QAction *linear2 = new QAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Linear"), this); linear2->setData((int)mlt_keyframe_linear); linear2->setCheckable(true); kfType->addAction(linear2); QAction *curve2 = new QAction(QIcon::fromTheme(QStringLiteral("smooth")), i18n("Smooth"), this); curve2->setData((int)mlt_keyframe_smooth); curve2->setCheckable(true); kfType->addAction(curve2); switch (KdenliveSettings::defaultkeyframeinterp()) { case mlt_keyframe_discrete: kfType->setCurrentAction(discrete2); break; case mlt_keyframe_smooth: kfType->setCurrentAction(curve2); break; default: kfType->setCurrentAction(linear2); break; } connect(kfType, static_cast(&KSelectAction::triggered), [&](QAction *ac) { KdenliveSettings::setDefaultkeyframeinterp(ac->data().toInt()); }); auto *container = new QMenu(this); container->addAction(copy); container->addAction(paste); container->addSeparator(); container->addAction(kfType); container->addAction(removeNext); // Menu toolbutton auto *menuButton = new QToolButton(this); menuButton->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu"))); menuButton->setToolTip(i18n("Options")); menuButton->setMenu(container); menuButton->setPopupMode(QToolButton::InstantPopup); m_toolbar->addWidget(menuButton); m_toolbar->addWidget(m_time); m_lay->addWidget(m_keyframeview); m_lay->addWidget(m_toolbar); monitorSeek(monitor->position()); 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); connect(monitor, &Monitor::seekToNextKeyframe, m_keyframeview, &KeyframeView::slotGoToNext, Qt::UniqueConnection); connect(monitor, &Monitor::seekToPreviousKeyframe, m_keyframeview, &KeyframeView::slotGoToPrev, Qt::UniqueConnection); connect(monitor, &Monitor::addRemoveKeyframe, m_keyframeview, &KeyframeView::slotAddRemove, Qt::UniqueConnection); } KeyframeWidget::~KeyframeWidget() { delete m_keyframeview; delete m_buttonAddDelete; delete m_buttonPrevious; delete m_buttonNext; delete m_time; } void KeyframeWidget::monitorSeek(int pos) { int in = pCore->getItemPosition(m_model->getOwnerId()); int out = in + pCore->getItemDuration(m_model->getOwnerId()); bool isInRange = pos >= in && pos < out; m_buttonAddDelete->setEnabled(isInRange && pos > in); connectMonitor(isInRange); int framePos = qBound(in, pos, out) - in; if (isInRange && framePos != m_time->getValue()) { slotSetPosition(framePos, false); } } void KeyframeWidget::slotEditKeyframeType(QAction *action) { int type = action->data().toInt(); m_keyframeview->slotEditType(type, m_index); } void KeyframeWidget::slotRefreshParams() { int pos = getPosition(); KeyframeType keyType = m_keyframes->keyframeType(GenTime(pos, pCore->getCurrentFps())); int i = 0; while (auto ac = m_selectType->action(i)) { if (ac->data().toInt() == (int)keyType) { m_selectType->setCurrentItem(i); break; } i++; } for (const auto &w : m_parameters) { auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value(); if (type == ParamType::KeyframeParam) { ((DoubleWidget *)w.second)->setValue(m_keyframes->getInterpolatedValue(pos, w.first).toDouble()); } else if (type == ParamType::AnimatedRect) { const QString val = m_keyframes->getInterpolatedValue(pos, w.first).toString(); const QStringList vals = val.split(QLatin1Char(' ')); QRect rect; double opacity = -1; if (vals.count() >= 4) { rect = QRect(vals.at(0).toInt(), vals.at(1).toInt(), vals.at(2).toInt(), vals.at(3).toInt()); if (vals.count() > 4) { QLocale locale; opacity = locale.toDouble(vals.at(4)); } } ((GeometryWidget *)w.second)->setValue(rect, opacity); } } if (m_monitorHelper) { m_monitorHelper->refreshParams(pos); return; } } void KeyframeWidget::slotSetPosition(int pos, bool update) { if (pos < 0) { pos = m_time->getValue(); m_keyframeview->slotSetPosition(pos, true); } else { m_time->setValue(pos); m_keyframeview->slotSetPosition(pos, true); } m_buttonAddDelete->setEnabled(pos > 0); slotRefreshParams(); if (update) { emit seekToPos(pos); } } int KeyframeWidget::getPosition() const { return m_time->getValue() + pCore->getItemIn(m_model->getOwnerId()); } 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, bool singleKeyframe) { if (atKeyframe) { m_buttonAddDelete->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); m_buttonAddDelete->setToolTip(i18n("Delete keyframe")); } else { m_buttonAddDelete->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_buttonAddDelete->setToolTip(i18n("Add keyframe")); } pCore->getMonitor(m_model->monitorId)->setEffectKeyframe(atKeyframe || singleKeyframe); m_selectType->setEnabled(atKeyframe || singleKeyframe); for (const auto &w : m_parameters) { w.second->setEnabled(atKeyframe || singleKeyframe); } } void KeyframeWidget::slotRefresh() { // update duration bool ok = false; int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); Q_ASSERT(ok); // m_model->dataChanged(QModelIndex(), QModelIndex()); //->getKeyframeModel()->getKeyModel(m_index)->dataChanged(QModelIndex(), QModelIndex()); m_keyframeview->setDuration(duration); m_time->setRange(0, duration - 1); slotRefreshParams(); } void KeyframeWidget::resetKeyframes() { // update duration bool ok = false; int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); Q_ASSERT(ok); // reset keyframes m_keyframes->refresh(); // m_model->dataChanged(QModelIndex(), QModelIndex()); m_keyframeview->setDuration(duration); m_time->setRange(0, duration - 1); slotRefreshParams(); } void KeyframeWidget::addParameter(const QPersistentModelIndex &index) { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); // Retrieve parameters from the model QString name = m_model->data(index, Qt::DisplayRole).toString(); QString comment = m_model->data(index, AssetParameterModel::CommentRole).toString(); QString suffix = m_model->data(index, AssetParameterModel::SuffixRole).toString(); auto type = m_model->data(index, AssetParameterModel::TypeRole).value(); // Construct object QWidget *paramWidget = nullptr; if (type == ParamType::AnimatedRect) { m_neededScene = MonitorSceneType::MonitorSceneGeometry; int inPos = m_model->data(index, AssetParameterModel::ParentInRole).toInt(); QPair range(inPos, inPos + m_model->data(index, AssetParameterModel::ParentDurationRole).toInt()); QSize frameSize = pCore->getCurrentFrameSize(); const QString value = m_keyframes->getInterpolatedValue(getPosition(), index).toString(); QRect rect; double opacity = 0; QStringList vals = value.split(QLatin1Char(' ')); if (vals.count() >= 4) { rect = QRect(vals.at(0).toInt(), vals.at(1).toInt(), vals.at(2).toInt(), vals.at(3).toInt()); if (vals.count() > 4) { opacity = locale.toDouble(vals.at(4)); } } // qtblend uses an opacity value in the (0-1) range, while older geometry effects use (0-100) bool integerOpacity = m_model->getAssetId() != QLatin1String("qtblend"); GeometryWidget *geomWidget = new GeometryWidget(pCore->getMonitor(m_model->monitorId), range, rect, opacity, frameSize, false, m_model->data(m_index, AssetParameterModel::OpacityRole).toBool(), integerOpacity, this); connect(geomWidget, &GeometryWidget::valueChanged, [this, index](const QString v) { m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index); }); paramWidget = geomWidget; } else if (type == ParamType::Roto_spline) { m_monitorHelper = new RotoHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this); connect(m_monitorHelper, &KeyframeMonitorHelper::updateKeyframeData, this, &KeyframeWidget::slotUpdateKeyframesFromMonitor, Qt::UniqueConnection); m_neededScene = MonitorSceneType::MonitorSceneRoto; } else { if (m_model->getAssetId() == QLatin1String("frei0r.c0rners")) { if (m_neededScene == MonitorSceneDefault && !m_monitorHelper) { m_neededScene = MonitorSceneType::MonitorSceneCorners; m_monitorHelper = new CornersHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this); connect(m_monitorHelper, &KeyframeMonitorHelper::updateKeyframeData, this, &KeyframeWidget::slotUpdateKeyframesFromMonitor, Qt::UniqueConnection); connect(this, &KeyframeWidget::addIndex, m_monitorHelper, &CornersHelper::addIndex); } else { if (type == ParamType::KeyframeParam) { int paramName = m_model->data(index, AssetParameterModel::NameRole).toInt(); if (paramName < 8) { emit addIndex(index); } } } } double value = m_keyframes->getInterpolatedValue(getPosition(), index).toDouble(); double min = locale.toDouble(m_model->data(index, AssetParameterModel::MinRole).toString()); double max = locale.toDouble(m_model->data(index, AssetParameterModel::MaxRole).toString()); double defaultValue = m_model->data(index, AssetParameterModel::DefaultRole).toDouble(); int decimals = m_model->data(index, AssetParameterModel::DecimalsRole).toInt(); double factor = locale.toDouble(m_model->data(index, AssetParameterModel::FactorRole).toString()); factor = qFuzzyIsNull(factor) ? 1 : factor; auto doubleWidget = new DoubleWidget(name, value, min, max, factor, defaultValue, comment, -1, suffix, decimals, this); connect(doubleWidget, &DoubleWidget::valueChanged, [this, index](double v) { m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index); }); paramWidget = doubleWidget; } if (paramWidget) { m_parameters[index] = paramWidget; m_lay->addWidget(paramWidget); } } void KeyframeWidget::slotInitMonitor(bool active) { Monitor *monitor = pCore->getMonitor(m_model->monitorId); if (m_keyframeview) { m_keyframeview->initKeyframePos(); connect(monitor, &Monitor::updateScene, m_keyframeview, &KeyframeView::slotModelChanged, Qt::UniqueConnection); } connectMonitor(active); if (active) { connect(monitor, &Monitor::seekPosition, this, &KeyframeWidget::monitorSeek, Qt::UniqueConnection); } else { disconnect(monitor, &Monitor::seekPosition, this, &KeyframeWidget::monitorSeek); } } void KeyframeWidget::connectMonitor(bool active) { if (m_monitorHelper) { if (m_monitorHelper->connectMonitor(active)) { slotRefreshParams(); } } for (const auto &w : m_parameters) { auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value(); if (type == ParamType::AnimatedRect) { ((GeometryWidget *)w.second)->connectMonitor(active); break; } } } void KeyframeWidget::slotUpdateKeyframesFromMonitor(const QPersistentModelIndex &index, const QVariant &res) { - GenTime pos(getPosition(), pCore->getCurrentFps()); if (m_keyframes->isEmpty()) { + // Always ensure first keyframe is at clip start + GenTime pos(pCore->getItemIn(m_model->getOwnerId()), pCore->getCurrentFps()); m_keyframes->addKeyframe(pos, KeyframeType::Linear); m_keyframes->updateKeyframe(pos, res, index); } else if (m_keyframes->hasKeyframe(getPosition()) || m_keyframes->singleKeyframe()) { + GenTime pos(getPosition(), pCore->getCurrentFps()); m_keyframes->updateKeyframe(pos, res, index); } } MonitorSceneType KeyframeWidget::requiredScene() const { qDebug() << "// // // RESULTING REQUIRED SCENE: " << m_neededScene; return m_neededScene; } bool KeyframeWidget::keyframesVisible() const { return m_keyframeview->isVisible(); } void KeyframeWidget::showKeyframes(bool enable) { m_toolbar->setVisible(enable); m_keyframeview->setVisible(enable); } void KeyframeWidget::slotCopyKeyframes() { QJsonDocument effectDoc = m_model->toJson(false); if (effectDoc.isEmpty()) { return; } QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(QString(effectDoc.toJson())); } void KeyframeWidget::slotImportKeyframes() { QClipboard *clipboard = QApplication::clipboard(); QString values = clipboard->text(); QList indexes; for (const auto &w : m_parameters) { indexes << w.first; } QPointer import = new KeyframeImport(values, m_model, indexes, this); if (import->exec() != QDialog::Accepted) { delete import; return; } import->importSelectedData(); /*m_model->getKeyframeModel()->getKeyModel()->dataChanged(QModelIndex(), QModelIndex());*/ /*m_model->modelChanged(); qDebug()<<"//// UPDATING KEYFRAMES CORE---------"; pCore->updateItemKeyframes(m_model->getOwnerId());*/ delete import; } void KeyframeWidget::slotRemoveNextKeyframes() { m_keyframes->removeNextKeyframes(GenTime(m_time->getValue(), pCore->getCurrentFps())); }