diff --git a/data/effects/avfilter/CMakeLists.txt b/data/effects/avfilter/CMakeLists.txt index e10405b96..15f8c218d 100644 --- a/data/effects/avfilter/CMakeLists.txt +++ b/data/effects/avfilter/CMakeLists.txt @@ -1,8 +1,9 @@ INSTALL (FILES acompressor.xml aecho.xml agate.xml selectivecolor.xml avfilter_lut3d.xml +unsharp.xml DESTINATION ${DATA_INSTALL_DIR}/kdenlive/effects) diff --git a/data/effects/avfilter/unsharp.xml b/data/effects/avfilter/unsharp.xml new file mode 100644 index 000000000..4c5f0edcf --- /dev/null +++ b/data/effects/avfilter/unsharp.xml @@ -0,0 +1,24 @@ + + + Unsharp (avfilter) + Sharpen or blur your video + libavfilter + + Luma horizontal matrix + + + Luma vertical matrix + + + Luma strength + + + Chroma horizontal matrix + + + Chroma vertical matrix + + + Chroma strength + + diff --git a/src/assets/model/assetparametermodel.cpp b/src/assets/model/assetparametermodel.cpp index a1a6015e5..c761a55c5 100644 --- a/src/assets/model/assetparametermodel.cpp +++ b/src/assets/model/assetparametermodel.cpp @@ -1,863 +1,865 @@ /*************************************************************************** * 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")); m_isAudio = assetXml.attribute(QStringLiteral("type")) == QLatin1String("audio"); 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.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) - 1; 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 = i18n(currentParameter.firstChildElement(QStringLiteral("name")).text().toUtf8().data()); 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); if (!m_isAudio) { // 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); if (!m_isAudio) { // 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 OddRole: + return element.attribute(QStringLiteral("odd")) == QLatin1String("1"); 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); int frame_duration = pCore->getDurationFromString(KdenliveSettings::fade_duration()); // 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)) .replace(QLatin1String("%fade"), QString::number(frame_duration)); 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) { if (attribute == QLatin1String("default")) { return content.toDouble(); } 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.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.type() == QVariant::Double ? QJsonValue(fixed.second.toDouble()) : QJsonValue(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"), param.second.value.type() == QVariant::Double ? QJsonValue(param.second.value.toDouble()) : 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 } //TODO: delete file if there are no more presets in it 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/model/assetparametermodel.hpp b/src/assets/model/assetparametermodel.hpp index 850a15901..183413ee9 100644 --- a/src/assets/model/assetparametermodel.hpp +++ b/src/assets/model/assetparametermodel.hpp @@ -1,231 +1,232 @@ /*************************************************************************** * 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, ColorWheel, Position, Curve, Bezier_spline, Roto_spline, Wipe, Url, Keywords, Fontfamily, Filterjob, Readonly, Hidden }; Q_DECLARE_METATYPE(ParamType) class AssetParameterModel : public QAbstractListModel, public enable_shared_from_this_virtual { Q_OBJECT public: explicit AssetParameterModel(std::unique_ptr asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId, QObject *parent = nullptr); ~AssetParameterModel() override; enum DataRoles { NameRole = Qt::UserRole + 1, TypeRole, CommentRole, AlternateNameRole, MinRole, MaxRole, DefaultRole, SuffixRole, DecimalsRole, + OddRole, ValueRole, AlphaRole, ListValuesRole, ListNamesRole, FactorRole, FilterRole, FilterJobParamsRole, FilterParamsRole, ScaleRole, OpacityRole, RelativePosRole, // Don't display this param in timeline keyframes ShowInTimelineRole, InRole, OutRole, ParentInRole, ParentPositionRole, ParentDurationRole, HideKeyframesFirstRole, List1Role, List2Role, Enum1Role, Enum2Role, Enum3Role, Enum4Role, Enum5Role, Enum6Role, Enum7Role, Enum8Role, Enum9Role, Enum10Role, Enum11Role, Enum12Role, Enum13Role, Enum14Role, Enum15Role, Enum16Role }; /* @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 ¶mValue, bool update = true, const QModelIndex ¶mIndex = QModelIndex()); void setParameter(const QString &name, int value, bool update = true); /* @brief Return all the parameters as pairs (parameter name, parameter value) */ QVector> getAllParameters() const; /* @brief Returns a json definition of the effect with all param values */ QJsonDocument toJson(bool includeFixed = true) const; void savePreset(const QString &presetFile, const QString &presetName); void deletePreset(const QString &presetFile, const QString &presetName); const QStringList getPresetList(const QString &presetFile) const; const QVector> loadPreset(const QString &presetFile, const QString &presetName); /* @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 */ Q_INVOKABLE std::shared_ptr getKeyframeModel(); /* @brief Must be called before using the keyframes of this model */ void prepareKeyframes(); void resetAsset(std::unique_ptr asset); /* @brief Returns true if the effect has more than one keyframe */ bool hasMoreThanOneKeyframe() const; int time_to_frames(const QString &time); void passProperties(Mlt::Properties &target); /* @brief Returns a list of the parameter names that are keyframable */ QStringList getKeyframableParameters() const; 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 ObjectId &owner, const QString &attribute, const QDomElement &element, QVariant defaultValue = QVariant()); QVariant parseSubAttributes(const QString &attribute, const QDomElement &element) const; /* @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; }; QString m_assetId; ObjectId m_ownerId; std::vector m_paramOrder; // Keep track of parameter order, important for sox 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; // if true, keyframe tools will be hidden by default bool m_hideKeyframesByDefault; // true if this is an audio effect, used to prevent unnecessary monitor refresh / timeline invalidate bool m_isAudio; /* @brief Set the parameter with given name to the given value. This should be called when first * building an effect in the constructor, so that we don't call shared_from_this */ void internalSetParameter(const QString &name, const QString ¶mValue, const QModelIndex ¶mIndex = QModelIndex()); signals: void modelChanged(); /** @brief inform child effects (in case of bin effect with timeline producers) * that a change occurred and a param update is needed **/ void updateChildren(const QString &name); void compositionTrackChanged(); void replugEffect(std::shared_ptr asset); void rebuildEffect(std::shared_ptr asset); void enabledChange(bool); }; #endif diff --git a/src/assets/view/widgets/animationwidget.cpp b/src/assets/view/widgets/animationwidget.cpp index ba2eebe5a..39c6e1aaf 100644 --- a/src/assets/view/widgets/animationwidget.cpp +++ b/src/assets/view/widgets/animationwidget.cpp @@ -1,1687 +1,1687 @@ /*************************************************************************** * Copyright (C) 2016 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 "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mlt++/MltAnimation.h" #include "mlt++/MltProfile.h" #include "animationwidget.h" #include "assets/model/assetparametermodel.hpp" #include "core.h" #include "kdenlivesettings.h" #include "mltcontroller/effectscontroller.h" #include "monitor/monitor.h" #include "timecodedisplay.h" #include "widgets/doublewidget.h" #include "widgets/dragvalue.h" AnimationWidget::AnimationWidget(std::shared_ptr model, QModelIndex index, QPair range, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) , m_active(false) //, m_clipPos(clipPos) , m_editedKeyframe(-1) , m_attachedToEnd(-2) //, m_xml(xml) //, m_effectId(effectId) , m_spinX(nullptr) , m_spinY(nullptr) , m_spinWidth(nullptr) , m_spinHeight(nullptr) , m_spinSize(nullptr) , m_spinOpacity(nullptr) { setAcceptDrops(true); auto *vbox2 = new QVBoxLayout(this); // Keyframe ruler if (range.second >= 0) { m_inPoint = range.first; m_outPoint = range.second; m_offset = m_model->data(m_index, AssetParameterModel::InRole).toInt(); } else { m_offset = 0; m_inPoint = m_model->data(m_index, AssetParameterModel::InRole).toInt(); m_outPoint = m_model->data(m_index, AssetParameterModel::OutRole).toInt() + 1; } m_monitorSize = pCore->getCurrentFrameSize(); m_monitor = pCore->getMonitor(m_model->monitorId); m_timePos = new TimecodeDisplay(m_monitor->timecode(), this); m_ruler = new AnimKeyframeRuler(0, m_outPoint - m_inPoint, this); connect(m_ruler, &AnimKeyframeRuler::addKeyframe, this, &AnimationWidget::slotAddKeyframe); connect(m_ruler, &AnimKeyframeRuler::removeKeyframe, this, &AnimationWidget::slotDeleteKeyframe); vbox2->addWidget(m_ruler); vbox2->setContentsMargins(0, 0, 0, 0); auto *tb = new QToolBar(this); vbox2->addWidget(tb); setLayout(vbox2); connect(m_ruler, &AnimKeyframeRuler::requestSeek, this, &AnimationWidget::requestSeek); connect(m_ruler, &AnimKeyframeRuler::moveKeyframe, this, &AnimationWidget::moveKeyframe); connect(m_timePos, SIGNAL(timeCodeEditingFinished()), this, SLOT(slotPositionChanged())); connect(m_monitor, &Monitor::seekPosition, this, &AnimationWidget::monitorSeek, Qt::UniqueConnection); if (m_frameSize.isNull() || m_frameSize.width() == 0 || m_frameSize.height() == 0) { m_frameSize = m_monitorSize; } // seek to previous m_previous = tb->addAction(QIcon::fromTheme(QStringLiteral("media-skip-backward")), i18n("Previous keyframe"), this, SLOT(slotPrevious())); // Add/remove keyframe m_addKeyframe = new KDualAction(i18n("Add keyframe"), i18n("Remove keyframe"), this); m_addKeyframe->setInactiveIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_addKeyframe->setActiveIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); connect(m_addKeyframe, SIGNAL(activeChangedByUser(bool)), this, SLOT(slotAddDeleteKeyframe(bool))); tb->addAction(m_addKeyframe); // seek to next m_next = tb->addAction(QIcon::fromTheme(QStringLiteral("media-skip-forward")), i18n("Next keyframe"), this, SLOT(slotNext())); // Preset combo m_presetCombo = new QComboBox(this); m_presetCombo->setToolTip(i18n("Presets")); connect(m_presetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(applyPreset(int))); tb->addWidget(m_presetCombo); // Keyframe type widget m_selectType = new KSelectAction(QIcon::fromTheme(QStringLiteral("keyframes")), i18n("Keyframe interpolation"), this); 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 *linear = new QAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Linear"), this); linear->setData((int)mlt_keyframe_linear); linear->setCheckable(true); m_selectType->addAction(linear); 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, SIGNAL(triggered(QAction *)), this, SLOT(slotEditKeyframeType(QAction *))); KSelectAction *defaultInterp = new KSelectAction(QIcon::fromTheme(QStringLiteral("keyframes")), i18n("Default interpolation"), this); discrete = new QAction(QIcon::fromTheme(QStringLiteral("discrete")), i18n("Discrete"), this); discrete->setData((int)mlt_keyframe_discrete); discrete->setCheckable(true); defaultInterp->addAction(discrete); linear = new QAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Linear"), this); linear->setData((int)mlt_keyframe_linear); linear->setCheckable(true); defaultInterp->addAction(linear); curve = new QAction(QIcon::fromTheme(QStringLiteral("smooth")), i18n("Smooth"), this); curve->setData((int)mlt_keyframe_smooth); curve->setCheckable(true); defaultInterp->addAction(curve); switch (KdenliveSettings::defaultkeyframeinterp()) { case mlt_keyframe_discrete: defaultInterp->setCurrentAction(discrete); break; case mlt_keyframe_smooth: defaultInterp->setCurrentAction(curve); break; default: defaultInterp->setCurrentAction(linear); break; } connect(defaultInterp, SIGNAL(triggered(QAction *)), this, SLOT(slotSetDefaultInterp(QAction *))); m_selectType->setToolBarMode(KSelectAction::ComboBoxMode); m_endAttach = new QAction(i18n("Attach keyframe to end"), this); m_endAttach->setCheckable(true); connect(m_endAttach, &QAction::toggled, this, &AnimationWidget::slotReverseKeyframeType); // copy/paste keyframes from clipboard QAction *copy = new QAction(i18n("Copy keyframes to clipboard"), this); connect(copy, &QAction::triggered, this, &AnimationWidget::slotCopyKeyframes); QAction *paste = new QAction(i18n("Import keyframes from clipboard"), this); connect(paste, &QAction::triggered, this, &AnimationWidget::slotImportKeyframes); QAction *removeNext = new QAction(i18n("Remove all keyframes after cursor"), this); connect(removeNext, &QAction::triggered, this, &AnimationWidget::slotRemoveNext); // save preset QAction *savePreset = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save preset"), this); connect(savePreset, &QAction::triggered, this, &AnimationWidget::savePreset); // delete preset QAction *delPreset = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete preset"), this); connect(delPreset, &QAction::triggered, this, &AnimationWidget::deletePreset); auto *container = new QMenu; tb->addAction(m_selectType); container->addAction(m_endAttach); container->addSeparator(); container->addAction(copy); container->addAction(paste); container->addAction(removeNext); container->addSeparator(); container->addAction(savePreset); container->addAction(delPreset); container->addAction(defaultInterp); auto *menuButton = new QToolButton; menuButton->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu"))); menuButton->setToolTip(i18n("Options")); menuButton->setMenu(container); menuButton->setPopupMode(QToolButton::InstantPopup); tb->addWidget(menuButton); // Spacer QWidget *empty = new QWidget(); empty->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); tb->addWidget(empty); // Timecode tb->addWidget(m_timePos); m_timePos->setFrame(false); m_timePos->setRange(0, m_outPoint - m_inPoint - 1); // Prepare property mlt_profile profile = m_monitor->profile()->get_profile(); m_animProperties.set("_profile", profile, 0, nullptr, nullptr); // Display keyframe parameter addParameter(m_index); finishSetup(); // Update displayed values monitorSeek(m_monitor->position()); } AnimationWidget::~AnimationWidget() {} void AnimationWidget::finishSetup() { // Load effect presets loadPresets(); } // static QString AnimationWidget::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; } void AnimationWidget::updateTimecodeFormat() { m_timePos->slotUpdateTimeCodeFormat(); } void AnimationWidget::slotPrevious() { int previous = qMax(-m_offset, m_animController.previous_key(m_timePos->getValue() - m_offset - 1)) + m_offset; if (previous == m_timePos->getValue() && previous > 0) { // Make sure we can seek to effect start even if there is no keyframe previous = 0; } m_ruler->setActiveKeyframe(previous); slotPositionChanged(previous, true); } void AnimationWidget::slotNext() { int next = m_animController.next_key(m_timePos->getValue() - m_offset + 1) + m_offset; if (!m_animController.is_key(next - m_offset)) { // No keyframe after current pos, return end position next = m_timePos->maximum(); } else { m_ruler->setActiveKeyframe(next - m_offset); } slotPositionChanged(next, true); } void AnimationWidget::slotAddKeyframe(int pos) { slotAddDeleteKeyframe(true, pos); } void AnimationWidget::doAddKeyframe(int pos, QString paramName, bool directUpdate) { if (paramName.isEmpty()) { paramName = m_inTimeline; } if (pos == -1) { pos = m_timePos->getValue(); } pos -= m_offset; // Try to get previous key's type mlt_keyframe_type type; if (m_selectType->isVisible()) { type = (mlt_keyframe_type)KdenliveSettings::defaultkeyframeinterp(); if (m_animController.key_count() > 1) { int previous = m_animController.previous_key(pos); if (m_animController.is_key(previous)) { type = m_animController.keyframe_type(previous); } else { int next = m_animController.next_key(pos); if (m_animController.is_key(next)) { type = m_animController.keyframe_type(next); } } } } else { type = mlt_keyframe_linear; } if (paramName == m_rectParameter) { mlt_rect rect = m_animProperties.anim_get_rect(paramName.toUtf8().constData(), pos, m_outPoint); m_animProperties.anim_set(paramName.toUtf8().constData(), rect, pos, m_outPoint, type); } else { double val = m_animProperties.anim_get_double(paramName.toUtf8().constData(), pos, m_outPoint); m_animProperties.anim_set(paramName.toUtf8().constData(), val, pos, m_outPoint, type); } slotPositionChanged(-1, false); if (directUpdate) { m_ruler->setActiveKeyframe(pos); rebuildKeyframes(); emit valueChanged(m_index, QString(m_animController.serialize_cut()), true); } } void AnimationWidget::slotDeleteKeyframe(int pos) { slotAddDeleteKeyframe(false, pos); } void AnimationWidget::slotAddDeleteKeyframe(bool add, int pos) { if (pos == -1) { pos = m_timePos->getValue(); } QStringList paramNames = m_doubleWidgets.keys(); if (!m_rectParameter.isEmpty()) { paramNames << m_rectParameter; } if (!add) { // Delete keyframe in all animations at current pos for (int i = 0; i < paramNames.count(); i++) { m_animController = m_animProperties.get_animation(paramNames.at(i).toUtf8().constData()); if (m_animController.is_key(pos - m_offset)) { m_animController.remove(pos - m_offset); } } m_selectType->setEnabled(false); m_addKeyframe->setActive(false); slotPositionChanged(-1, false); } else { // Add keyframe in all animations for (int i = 0; i < paramNames.count(); i++) { m_animController = m_animProperties.get_animation(paramNames.at(i).toUtf8().constData()); if (!m_animController.is_key(pos - m_offset)) { doAddKeyframe(pos, paramNames.at(i), false); } } m_ruler->setActiveKeyframe(pos); } // Rebuild rebuildKeyframes(); // Send updates for (int i = 0; i < m_parameters.count(); i++) { m_animController = m_animProperties.get_animation(m_parameters.at(i).second.toUtf8().constData()); emit valueChanged(m_parameters.at(i).first, QString(m_animController.serialize_cut()), true); } // Restore default controller m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); } void AnimationWidget::slotRemoveNext() { int pos = m_timePos->getValue(); // Delete keyframe in all animations at current pos QStringList paramNames = m_doubleWidgets.keys(); if (!m_rectParameter.isEmpty()) { paramNames << m_rectParameter; } int kfrPos; for (int i = 0; i < paramNames.count(); i++) { m_animController = m_animProperties.get_animation(paramNames.at(i).toUtf8().constData()); int j = 0; while (j < m_animController.key_count()) { kfrPos = m_animController.key_get_frame(j); if (kfrPos > (pos - m_offset)) { m_animController.remove(kfrPos); } else { j++; } } } m_selectType->setEnabled(false); m_addKeyframe->setActive(false); slotPositionChanged(-1, false); // Send updates for (int i = 0; i < m_parameters.count(); i++) { m_animController = m_animProperties.get_animation(m_parameters.at(i).second.toUtf8().constData()); emit valueChanged(m_parameters.at(i).first, QString(m_animController.serialize_cut()), true); } // Restore default controller m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); // Rebuild rebuildKeyframes(); } void AnimationWidget::slotSyncPosition(int relTimelinePos) { // do only sync if this effect is keyframable if (m_timePos->maximum() > 0) { relTimelinePos = qBound(0, relTimelinePos, m_timePos->maximum()); slotPositionChanged(relTimelinePos, false); } } void AnimationWidget::moveKeyframe(int oldPos, int newPos) { bool isKey; mlt_keyframe_type type; if (m_animController.get_item(oldPos - m_offset, isKey, type) != 0) { qCDebug(KDENLIVE_LOG) << "////////ERROR NO KFR"; return; } if (!m_rectParameter.isEmpty()) { m_animController = m_animProperties.get_animation(m_rectParameter.toUtf8().constData()); mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), oldPos - m_offset, m_outPoint); m_animController.remove(oldPos - m_offset); m_animProperties.anim_set(m_rectParameter.toUtf8().constData(), rect, newPos - m_offset, m_outPoint, type); } QStringList paramNames = m_doubleWidgets.keys(); for (int i = 0; i < paramNames.count(); i++) { const QString ¶m = paramNames.at(i); m_animController = m_animProperties.get_animation(param.toUtf8().constData()); double val = m_animProperties.anim_get_double(param.toUtf8().constData(), oldPos - m_offset, m_outPoint); m_animController.remove(oldPos - m_offset); m_animProperties.anim_set(param.toUtf8().constData(), val, newPos - m_offset, m_outPoint, type); } m_ruler->setActiveKeyframe(newPos); if (m_attachedToEnd == oldPos) { m_attachedToEnd = newPos; } rebuildKeyframes(); slotPositionChanged(m_ruler->position(), false); // Send updates for (int i = 0; i < m_parameters.count(); i++) { m_animController = m_animProperties.get_animation(m_parameters.at(i).second.toUtf8().constData()); emit valueChanged(m_parameters.at(i).first, QString(m_animController.serialize_cut()), true); } // Restore default controller m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); } void AnimationWidget::rebuildKeyframes() { // Fetch keyframes QVector keyframes; QVector types; int frame; mlt_keyframe_type type; int count = m_animController.key_count(); for (int i = 0; i < count; i++) { if (m_animController.key_get(i, frame, type) == 0) { frame += m_offset; if (frame >= 0) { keyframes << frame; types << (count > 1 ? (int)type : mlt_keyframe_linear); } } } m_ruler->updateKeyframes(keyframes, types, m_attachedToEnd); } void AnimationWidget::updateToolbar() { int pos = m_timePos->getValue(); QMapIterator i(m_doubleWidgets); while (i.hasNext()) { i.next(); double val = m_animProperties.anim_get_double(i.key().toUtf8().constData(), pos, m_outPoint); i.value()->setValue(val * i.value()->factor); } if (m_animController.is_key(pos) && m_selectType->isVisible()) { QList types = m_selectType->actions(); for (int j = 0; j < types.count(); j++) { if (types.at(j)->data().toInt() == (int)m_animController.keyframe_type(pos)) { m_selectType->setCurrentAction(types.at(j)); break; } } m_selectType->setEnabled(m_animController.key_count() > 1); m_addKeyframe->setActive(true); m_addKeyframe->setEnabled(m_animController.key_count() > 1); if (m_doubleWidgets.value(m_inTimeline) != nullptr) { m_doubleWidgets.value(m_inTimeline)->enableEdit(true); } } else { m_selectType->setEnabled(false); m_addKeyframe->setActive(false); m_addKeyframe->setEnabled(true); if (m_doubleWidgets.value(m_inTimeline) != nullptr) { m_doubleWidgets.value(m_inTimeline)->enableEdit(false); } } } void AnimationWidget::slotPositionChanged(int pos, bool seek) { if (pos == -1) { pos = m_timePos->getValue(); } else { m_timePos->setValue(pos); } m_ruler->setValue(pos); if (m_spinX) { updateRect(pos); } updateSlider(pos); m_previous->setEnabled(pos > 0); m_next->setEnabled(pos < (m_outPoint - 1)); // scene ratio lock if ((m_spinWidth != nullptr) && m_spinWidth->isEnabled()) { double ratio = m_originalSize->isChecked() ? (double)m_frameSize.width() / m_frameSize.height() : (double)m_monitorSize.width() / m_monitorSize.height(); bool lockRatio = m_spinHeight->value() == (int)(m_spinWidth->value() / ratio + 0.5); m_lockRatio->blockSignals(true); m_lockRatio->setChecked(lockRatio); m_lockRatio->blockSignals(false); m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), m_lockRatio->isChecked() ? ratio : -1); } if (seek) { m_monitor->requestSeek(pos + m_inPoint); } } void AnimationWidget::requestSeek(int pos) { m_monitor->requestSeek(pos + m_inPoint); } void AnimationWidget::updateSlider(int pos) { m_endAttach->blockSignals(true); QMapIterator i(m_doubleWidgets); while (i.hasNext()) { i.next(); m_animController = m_animProperties.get_animation(i.key().toUtf8().constData()); double val = m_animProperties.anim_get_double(i.key().toUtf8().constData(), pos, m_outPoint); if (!m_animController.is_key(pos)) { // no keyframe m_addKeyframe->setEnabled(true); if (m_animController.key_count() <= 1) { // Special case: only one keyframe, allow adjusting whatever the position is i.value()->enableEdit(true); if (!i.value()->hasEditFocus()) { i.value()->setValue(val * i.value()->factor); } if (i.key() == m_inTimeline) { if (m_active) { m_monitor->setEffectKeyframe(true); } m_endAttach->setEnabled(true); m_endAttach->setChecked(m_attachedToEnd > -2 && m_animController.key_get_frame(0) >= m_attachedToEnd); } } else { i.value()->enableEdit(false); i.value()->setValue(val * i.value()->factor); if (i.key() == m_inTimeline) { if (m_active) { m_monitor->setEffectKeyframe(false); } m_endAttach->setEnabled(false); } } if (i.key() == m_inTimeline) { m_selectType->setEnabled(false); m_addKeyframe->setActive(false); } } else { // keyframe i.value()->setValue(val * i.value()->factor); if (i.key() == m_inTimeline) { if (m_active) { m_monitor->setEffectKeyframe(true); } m_addKeyframe->setActive(true); m_addKeyframe->setEnabled(m_animController.key_count() > 1); m_selectType->setEnabled(m_animController.key_count() > 1); m_endAttach->setEnabled(true); m_endAttach->setChecked(m_attachedToEnd > -2 && pos >= m_attachedToEnd); if (m_selectType->isVisible()) { mlt_keyframe_type currentType = m_animController.keyframe_type(pos); QList types = m_selectType->actions(); for (int j = 0; j < types.count(); j++) { if ((mlt_keyframe_type)types.at(j)->data().toInt() == currentType) { m_selectType->setCurrentAction(types.at(j)); break; } } } } i.value()->enableEdit(true); } } m_endAttach->blockSignals(false); // Restore default controller m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); } void AnimationWidget::updateRect(int pos) { m_endAttach->blockSignals(true); m_animController = m_animProperties.get_animation(m_rectParameter.toUtf8().constData()); mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), pos, m_outPoint); m_spinX->blockSignals(true); m_spinY->blockSignals(true); m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); m_spinSize->blockSignals(true); m_spinX->setValue(rect.x); m_spinY->setValue(rect.y); m_spinWidth->setValue(rect.w); m_spinHeight->setValue(rect.h); double size; if (rect.w / pCore->getCurrentDar() > rect.h) { if (m_originalSize->isChecked()) { size = rect.w * 100.0 / m_frameSize.width(); } else { size = rect.w * 100.0 / m_monitorSize.width(); } } else { if (m_originalSize->isChecked()) { size = rect.h * 100.0 / m_frameSize.height(); } else { size = rect.h * 100.0 / m_monitorSize.height(); } } m_spinSize->setValue(size); if (m_spinOpacity) { m_spinOpacity->blockSignals(true); m_spinOpacity->setValue(100.0 * rect.o); m_spinOpacity->blockSignals(false); } bool enableEdit = false; if (!m_animController.is_key(pos)) { // no keyframe m_addKeyframe->setEnabled(true); if (m_animController.key_count() <= 1) { // Special case: only one keyframe, allow adjusting whatever the position is enableEdit = true; if (m_active) { m_monitor->setEffectKeyframe(true); } m_endAttach->setEnabled(true); m_endAttach->setChecked(m_attachedToEnd > -2 && m_animController.key_get_frame(0) >= m_attachedToEnd); } else { enableEdit = false; if (m_active) { m_monitor->setEffectKeyframe(false); } m_endAttach->setEnabled(false); } m_selectType->setEnabled(false); m_addKeyframe->setActive(false); } else { // keyframe enableEdit = true; if (m_active) { m_monitor->setEffectKeyframe(true); } m_addKeyframe->setActive(true); m_addKeyframe->setEnabled(m_animController.key_count() > 1); m_selectType->setEnabled(m_animController.key_count() > 1); m_endAttach->setEnabled(true); m_endAttach->setChecked(m_attachedToEnd > -2 && pos >= m_attachedToEnd); if (m_selectType->isVisible()) { mlt_keyframe_type currentType = m_animController.keyframe_type(pos); QList types = m_selectType->actions(); for (int i = 0; i < types.count(); i++) { if ((mlt_keyframe_type)types.at(i)->data().toInt() == currentType) { m_selectType->setCurrentAction(types.at(i)); break; } } } } m_spinSize->setEnabled(enableEdit); m_spinX->setEnabled(enableEdit); m_spinY->setEnabled(enableEdit); m_spinWidth->setEnabled(enableEdit); m_spinHeight->setEnabled(enableEdit); m_spinSize->setEnabled(enableEdit); if (m_spinOpacity) { m_spinOpacity->setEnabled(enableEdit); } m_spinX->blockSignals(false); m_spinY->blockSignals(false); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); m_spinSize->blockSignals(false); m_endAttach->blockSignals(false); setupMonitor(QRect(rect.x, rect.y, rect.w, rect.h)); // Restore default controller m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); } void AnimationWidget::slotEditKeyframeType(QAction *action) { int pos = m_timePos->getValue() - m_offset; if (m_animController.is_key(pos)) { for (int i = 0; i < m_parameters.count(); i++) { m_animController = m_animProperties.get_animation(m_parameters.at(i).second.toUtf8().constData()); if (m_parameters.at(i).second == m_rectParameter) { mlt_rect rect = m_animProperties.anim_get_rect(m_parameters.at(i).second.toUtf8().constData(), pos, m_outPoint); m_animProperties.anim_set(m_parameters.at(i).second.toUtf8().constData(), rect, pos, m_outPoint, (mlt_keyframe_type)action->data().toInt()); } else { double val = m_animProperties.anim_get_double(m_parameters.at(i).second.toUtf8().constData(), pos, m_outPoint); m_animProperties.anim_set(m_parameters.at(i).second.toUtf8().constData(), val, pos, m_outPoint, (mlt_keyframe_type)action->data().toInt()); } emit valueChanged(m_parameters.at(i).first, QString(m_animController.serialize_cut()), true); } rebuildKeyframes(); setupMonitor(); // Restore default controller m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); } } void AnimationWidget::slotSetDefaultInterp(QAction *action) { KdenliveSettings::setDefaultkeyframeinterp(action->data().toInt()); } void AnimationWidget::addParameter(QModelIndex ix) { // Anim properties might at some point require some more infos like profile ParamType type = (ParamType)m_model->data(ix, AssetParameterModel::TypeRole).toInt(); QString keyframes = m_model->data(ix, AssetParameterModel::ValueRole).toString(); QString paramTag = m_model->data(ix, AssetParameterModel::NameRole).toString(); m_animProperties.set(paramTag.toUtf8().constData(), keyframes.toUtf8().constData()); m_attachedToEnd = KeyframeView::checkNegatives(keyframes, m_outPoint); if (type == ParamType::Animated || type == ParamType::RestrictedAnim) { // one dimension parameter // Required to initialize anim property m_inTimeline = paramTag; m_animProperties.anim_get_int(paramTag.toUtf8().constData(), 0, m_outPoint); buildSliderWidget(paramTag, ix); } else { // one dimension parameter // TODO: multiple rect parameters in effect ? m_rectParameter = paramTag; m_inTimeline = paramTag; // Required to initialize anim property m_animProperties.anim_get_rect(paramTag.toUtf8().constData(), 0, m_outPoint); buildRectWidget(paramTag, ix); } if (type == ParamType::RestrictedAnim) { // This param only support linear keyframes m_selectType->setVisible(false); m_selectType->setCurrentItem(0); } m_parameters << QPair(ix, paramTag); // Load keyframes rebuildKeyframes(); } void AnimationWidget::buildSliderWidget(const QString ¶mTag, QModelIndex ix) { QLocale locale; QString paramName = i18n(m_model->data(ix, Qt::DisplayRole).toString().toUtf8().data()); QString comment = m_model->data(ix, AssetParameterModel::CommentRole).toString(); if (!comment.isEmpty()) { comment = i18n(comment.toUtf8().data()); } int index = m_params.count() - 1; DoubleWidget *doubleparam = new DoubleWidget( paramName, 0, m_model->data(ix, AssetParameterModel::MinRole).toDouble(), m_model->data(ix, AssetParameterModel::MaxRole).toDouble(), m_model->data(ix, AssetParameterModel::FactorRole).toDouble() m_model->data(ix, AssetParameterModel::DefaultRole).toDouble(), comment, index, - m_model->data(ix, AssetParameterModel::SuffixRole).toString(), m_model->data(ix, AssetParameterModel::DecimalsRole).toInt(), this); + m_model->data(ix, AssetParameterModel::SuffixRole).toString(), m_model->data(ix, AssetParameterModel::DecimalsRole).toInt(), m_model->data(ix, AssetParameterModel::OddRole).toBool(), this); doubleparam->setObjectName(paramTag); doubleparam->setProperty("index", ix); connect(doubleparam, &DoubleWidget::valueChanged, this, &AnimationWidget::slotAdjustKeyframeValue); layout()->addWidget(doubleparam); // TODO: in timeline /*if ((!e.hasAttribute(QStringLiteral("intimeline")) || e.attribute(QStringLiteral("intimeline")) == QLatin1String("1")) && !e.hasAttribute(QStringLiteral("notintimeline"))) {*/ { m_inTimeline = paramTag; m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); } m_doubleWidgets.insert(paramTag, doubleparam); } void AnimationWidget::buildRectWidget(const QString ¶mTag, QModelIndex ix) { QString paramName = i18n(paramTag.toUtf8().data()); QString comment = m_model->data(ix, AssetParameterModel::CommentRole).toString(); if (!comment.isEmpty()) { comment = i18n(comment.toUtf8().data()); } auto *horLayout = new QHBoxLayout; - m_spinX = new DragValue(i18nc("x axis position", "X"), 0, 0, -99000, 99000, -1, QString(), false, this); + m_spinX = new DragValue(i18nc("x axis position", "X"), 0, 0, -99000, 99000, -1, QString(), false, false, this); connect(m_spinX, &DragValue::valueChanged, this, &AnimationWidget::slotAdjustRectKeyframeValue); horLayout->addWidget(m_spinX); m_spinX->setProperty("index", ix); - m_spinY = new DragValue(i18nc("y axis position", "Y"), 0, 0, -99000, 99000, -1, QString(), false, this); + m_spinY = new DragValue(i18nc("y axis position", "Y"), 0, 0, -99000, 99000, -1, QString(), false, false, this); connect(m_spinY, &DragValue::valueChanged, this, &AnimationWidget::slotAdjustRectKeyframeValue); horLayout->addWidget(m_spinY); - m_spinWidth = new DragValue(i18nc("Frame width", "W"), m_monitorSize.width(), 0, 1, 99000, -1, QString(), false, this); + m_spinWidth = new DragValue(i18nc("Frame width", "W"), m_monitorSize.width(), 0, 1, 99000, -1, QString(), false, false, this); connect(m_spinWidth, &DragValue::valueChanged, this, &AnimationWidget::slotAdjustRectWidth); horLayout->addWidget(m_spinWidth); // Lock ratio stuff m_lockRatio = new QAction(QIcon::fromTheme(QStringLiteral("link")), i18n("Lock aspect ratio"), this); m_lockRatio->setCheckable(true); connect(m_lockRatio, &QAction::triggered, this, &AnimationWidget::slotLockRatio); auto *ratioButton = new QToolButton; ratioButton->setDefaultAction(m_lockRatio); horLayout->addWidget(ratioButton); - m_spinHeight = new DragValue(i18nc("Frame height", "H"), m_monitorSize.height(), 0, 1, 99000, -1, QString(), false, this); + m_spinHeight = new DragValue(i18nc("Frame height", "H"), m_monitorSize.height(), 0, 1, 99000, -1, QString(), false, false, this); connect(m_spinHeight, &DragValue::valueChanged, this, &AnimationWidget::slotAdjustRectHeight); horLayout->addWidget(m_spinHeight); horLayout->addStretch(10); auto *horLayout2 = new QHBoxLayout; - m_spinSize = new DragValue(i18n("Size"), 100, 2, 1, 99000, -1, i18n("%"), false, this); + m_spinSize = new DragValue(i18n("Size"), 100, 2, 1, 99000, -1, i18n("%"), false, false, this); m_spinSize->setStep(10); connect(m_spinSize, &DragValue::valueChanged, this, &AnimationWidget::slotResize); horLayout2->addWidget(m_spinSize); if (m_model->data(ix, AssetParameterModel::OpacityRole).toBool()) { - m_spinOpacity = new DragValue(i18n("Opacity"), 100, 0, 0, 100, -1, i18n("%"), true, this); + m_spinOpacity = new DragValue(i18n("Opacity"), 100, 0, 0, 100, -1, i18n("%"), true, false, this); connect(m_spinOpacity, &DragValue::valueChanged, this, &AnimationWidget::slotAdjustRectKeyframeValue); horLayout2->addWidget(m_spinOpacity); } // Build buttons m_originalSize = new QAction(QIcon::fromTheme(QStringLiteral("zoom-original")), i18n("Adjust to original size"), this); connect(m_originalSize, &QAction::triggered, this, &AnimationWidget::slotAdjustToSource); m_originalSize->setCheckable(true); QAction *adjustSize = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-best")), i18n("Adjust and center in frame"), this); connect(adjustSize, &QAction::triggered, this, &AnimationWidget::slotAdjustToFrameSize); QAction *fitToWidth = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-width")), i18n("Fit to width"), this); connect(fitToWidth, &QAction::triggered, this, &AnimationWidget::slotFitToWidth); QAction *fitToHeight = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-height")), i18n("Fit to height"), this); connect(fitToHeight, &QAction::triggered, this, &AnimationWidget::slotFitToHeight); QAction *alignleft = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-left")), i18n("Align left"), this); connect(alignleft, &QAction::triggered, this, &AnimationWidget::slotMoveLeft); QAction *alignhcenter = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-hor")), i18n("Center horizontally"), this); connect(alignhcenter, &QAction::triggered, this, &AnimationWidget::slotCenterH); QAction *alignright = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-right")), i18n("Align right"), this); connect(alignright, &QAction::triggered, this, &AnimationWidget::slotMoveRight); QAction *aligntop = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-top")), i18n("Align top"), this); connect(aligntop, &QAction::triggered, this, &AnimationWidget::slotMoveTop); QAction *alignvcenter = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-vert")), i18n("Center vertically"), this); connect(alignvcenter, &QAction::triggered, this, &AnimationWidget::slotCenterV); QAction *alignbottom = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-bottom")), i18n("Align bottom"), this); connect(alignbottom, &QAction::triggered, this, &AnimationWidget::slotMoveBottom); auto *alignLayout = new QHBoxLayout; alignLayout->setSpacing(0); auto *alignButton = new QToolButton; alignButton->setDefaultAction(alignleft); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(alignhcenter); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(alignright); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(aligntop); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(alignvcenter); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(alignbottom); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(m_originalSize); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(adjustSize); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(fitToWidth); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(fitToHeight); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignLayout->addStretch(10); static_cast(layout())->addLayout(horLayout); static_cast(layout())->addLayout(alignLayout); static_cast(layout())->addLayout(horLayout2); m_animController = m_animProperties.get_animation(paramTag.toUtf8().constData()); } void AnimationWidget::slotUpdateVisibleParameter(bool display) { if (!display) { return; } DoubleWidget *slider = qobject_cast(QObject::sender()); if (slider) { if (slider->objectName() == m_inTimeline) { return; } m_inTimeline = slider->objectName(); m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); rebuildKeyframes(); emit valueChanged(m_index, QString(m_animController.serialize_cut()), true); } } void AnimationWidget::slotAdjustKeyframeValue(double value) { DoubleWidget *slider = qobject_cast(QObject::sender()); if (!slider) { return; } m_inTimeline = slider->objectName(); QModelIndex ix = slider->property("index").toModelIndex(); m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); int pos = m_ruler->position() - m_offset; mlt_keyframe_type type = m_selectType->isVisible() ? (m_selectType->isEnabled() ? (mlt_keyframe_type)m_selectType->currentAction()->data().toInt() : (mlt_keyframe_type)KdenliveSettings::defaultkeyframeinterp()) : mlt_keyframe_linear; if (m_animController.is_key(pos)) { // This is a keyframe type = m_animController.keyframe_type(pos); m_animProperties.anim_set(m_inTimeline.toUtf8().constData(), value / slider->factor, pos, m_outPoint, type); emit valueChanged(ix, QString(m_animController.serialize_cut()), true); } else if (m_animController.key_count() <= 1) { pos = m_animController.key_get_frame(0); if (pos >= 0) { if (m_animController.is_key(pos)) { type = m_animController.keyframe_type(pos); } m_animProperties.anim_set(m_inTimeline.toUtf8().constData(), value / slider->factor, pos, m_outPoint, type); emit valueChanged(ix, QString(m_animController.serialize_cut()), true); } } } void AnimationWidget::slotAdjustRectKeyframeValue() { m_animController = m_animProperties.get_animation(m_rectParameter.toUtf8().constData()); m_inTimeline = m_rectParameter; int pos = m_ruler->position() - m_offset; mlt_rect rect; rect.x = m_spinX->value(); rect.y = m_spinY->value(); rect.w = m_spinWidth->value(); rect.h = m_spinHeight->value(); rect.o = m_spinOpacity ? m_spinOpacity->value() / 100.0 : DBL_MIN; double size; if (m_spinWidth->value() / pCore->getCurrentDar() > m_spinHeight->value()) { if (m_originalSize->isChecked()) { size = m_spinWidth->value() * 100.0 / m_frameSize.width(); } else { size = m_spinWidth->value() * 100.0 / m_monitorSize.width(); } } else { if (m_originalSize->isChecked()) { size = m_spinHeight->value() * 100.0 / m_frameSize.height(); } else { size = m_spinHeight->value() * 100.0 / m_monitorSize.height(); } } m_spinSize->blockSignals(true); m_spinSize->setValue(size); m_spinSize->blockSignals(false); if (m_animController.is_key(pos)) { // This is a keyframe m_animProperties.anim_set(m_rectParameter.toUtf8().constData(), rect, pos, m_outPoint, (mlt_keyframe_type)m_selectType->currentAction()->data().toInt()); emit valueChanged(m_index, QString(m_animController.serialize_cut()), true); setupMonitor(QRect(rect.x, rect.y, rect.w, rect.h)); } else if (m_animController.key_count() <= 1) { pos = m_animController.key_get_frame(0); if (pos >= 0) { m_animProperties.anim_set(m_rectParameter.toUtf8().constData(), rect, pos, m_outPoint, (mlt_keyframe_type)m_selectType->currentAction()->data().toInt()); emit valueChanged(m_index, QString(m_animController.serialize_cut()), true); setupMonitor(QRect(rect.x, rect.y, rect.w, rect.h)); } } } void AnimationWidget::slotResize(double value) { m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); int w = m_originalSize->isChecked() ? m_frameSize.width() : m_monitorSize.width(); int h = m_originalSize->isChecked() ? m_frameSize.height() : m_monitorSize.height(); m_spinWidth->setValue(w * value / 100.0); m_spinHeight->setValue(h * value / 100.0); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); setupMonitor(); } bool AnimationWidget::isActive(const QString &name) const { return name == m_inTimeline; } const QMap AnimationWidget::getAnimation() { QMap animationResults; if (m_spinX) { m_animController = m_animProperties.get_animation(m_rectParameter.toUtf8().constData()); // TODO: keyframes attached to end animationResults.insert(m_rectParameter, m_animController.serialize_cut()); } QMapIterator i(m_doubleWidgets); while (i.hasNext()) { i.next(); m_animController = m_animProperties.get_animation(i.key().toUtf8().constData()); // no negative keyframe trick, return simple serialize if (m_attachedToEnd == -2) { animationResults.insert(i.key(), m_animController.serialize_cut()); } else { // Do processing ourselves to include negative values for keyframes relative to end int pos; mlt_keyframe_type type; QString key; QLocale locale; QStringList result; int duration = m_outPoint; for (int j = 0; j < m_animController.key_count(); ++j) { m_animController.key_get(j, pos, type); double val = m_animProperties.anim_get_double(i.key().toUtf8().constData(), pos, duration); if (pos >= m_attachedToEnd) { pos = qMin(pos - duration, -1); } key = QString::number(pos); switch (type) { case mlt_keyframe_discrete: key.append(QStringLiteral("|=")); break; case mlt_keyframe_smooth: key.append(QStringLiteral("~=")); break; default: key.append(QStringLiteral("=")); break; } key.append(locale.toString(val)); result << key; } animationResults.insert(i.key(), result.join(QLatin1Char(';'))); } } // restore original controller m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); return animationResults; } void AnimationWidget::slotReverseKeyframeType(bool reverse) { int pos = m_timePos->getValue(); if (m_animController.is_key(pos)) { if (reverse) { m_attachedToEnd = pos; } else { m_attachedToEnd = -2; } rebuildKeyframes(); emit valueChanged(m_index, QString(m_animController.serialize_cut()), true); } } void AnimationWidget::loadPresets(QString currentText) { m_presetCombo->blockSignals(true); QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/")); if (currentText.isEmpty()) { currentText = m_presetCombo->currentText(); } while (m_presetCombo->count() > 0) { m_presetCombo->removeItem(0); } m_presetCombo->removeItem(0); QMap defaultEntry; QStringList paramNames = m_doubleWidgets.keys(); for (int i = 0; i < paramNames.count(); i++) { defaultEntry.insert(paramNames.at(i), getDefaultKeyframes(m_offset, m_model->data(m_index, AssetParameterModel::DefaultRole).toString())); } m_presetCombo->addItem(i18n("Default"), defaultEntry); loadPreset(dir.absoluteFilePath(m_model->data(m_index, AssetParameterModel::TypeRole).toString())); loadPreset(dir.absoluteFilePath(m_effectId)); if (!currentText.isEmpty()) { int ix = m_presetCombo->findText(currentText); if (ix >= 0) { m_presetCombo->setCurrentIndex(ix); } } m_presetCombo->blockSignals(false); } void AnimationWidget::loadPreset(const QString &path) { KConfig confFile(path, KConfig::SimpleConfig); const QStringList groups = confFile.groupList(); for (const QString &grp : groups) { QMap entries = KConfigGroup(&confFile, grp).entryMap(); QMap variantEntries; QMapIterator i(entries); while (i.hasNext()) { i.next(); variantEntries.insert(i.key(), i.value()); } m_presetCombo->addItem(grp, variantEntries); } } void AnimationWidget::applyPreset(int ix) { QMap entries = m_presetCombo->itemData(ix).toMap(); QStringList presetNames = entries.keys(); QStringList paramNames = m_doubleWidgets.keys(); for (int i = 0; i < paramNames.count() && i < presetNames.count(); i++) { QString keyframes = entries.value(paramNames.at(i)).toString(); if (!keyframes.isEmpty()) { m_animProperties.set(paramNames.at(i).toUtf8().constData(), keyframes.toUtf8().constData()); // Required to initialize anim property m_animProperties.anim_get_int(m_inTimeline.toUtf8().constData(), 0, m_outPoint); } } if (!m_rectParameter.isEmpty()) { QString keyframes = entries.value(m_rectParameter).toString(); if (!keyframes.isEmpty()) { m_animProperties.set(m_rectParameter.toUtf8().constData(), keyframes.toUtf8().constData()); m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), 0, m_outPoint); } } m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData()); rebuildKeyframes(); emit valueChanged(m_index, QString(m_animController.serialize_cut()), true); } void AnimationWidget::savePreset() { QDialog d(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save); auto *l = new QVBoxLayout; d.setLayout(l); QLineEdit effectName(&d); effectName.setPlaceholderText(i18n("Enter preset name")); QCheckBox cb(i18n("Save as global preset (available to all effects)"), &d); l->addWidget(&effectName); l->addWidget(&cb); l->addWidget(buttonBox); d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject); d.connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept); if (d.exec() != QDialog::Accepted) { return; } QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } QString fileName = cb.isChecked() ? dir.absoluteFilePath(m_xml.attribute(QStringLiteral("type"))) : m_effectId; KConfig confFile(dir.absoluteFilePath(fileName), KConfig::SimpleConfig); KConfigGroup grp(&confFile, effectName.text()); // Parse keyframes QMap currentKeyframes = getAnimation(); QMapIterator i(currentKeyframes); while (i.hasNext()) { i.next(); grp.writeEntry(i.key(), i.value()); } confFile.sync(); loadPresets(effectName.text()); } void AnimationWidget::deletePreset() { QString effectName = m_presetCombo->currentText(); // try deleting as effect preset first QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/")); KConfig confFile(dir.absoluteFilePath(m_effectId), KConfig::SimpleConfig); KConfigGroup grp(&confFile, effectName); if (grp.exists()) { } else { // try global preset grp = KConfigGroup(&confFile, m_xml.attribute(QStringLiteral("type"))); } grp.deleteGroup(); confFile.sync(); loadPresets(); } void AnimationWidget::setActiveKeyframe(int frame) { m_ruler->setActiveKeyframe(frame); } void AnimationWidget::slotUpdateGeometryRect(const QRect r) { int pos = m_timePos->getValue(); if (!m_animController.is_key(pos)) { // no keyframe if (m_animController.key_count() <= 1) { // Special case: only one keyframe, allow adjusting whatever the position is pos = m_animController.key_get_frame(0); if (pos < 0) { // error qCDebug(KDENLIVE_LOG) << "* * *NOT on a keyframe, something is wrong"; return; } } } m_spinX->blockSignals(true); m_spinY->blockSignals(true); m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); m_spinX->setValue(r.x()); m_spinY->setValue(r.y()); m_spinWidth->setValue(r.width()); m_spinHeight->setValue(r.height()); m_spinX->blockSignals(false); m_spinY->blockSignals(false); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); setupMonitor(); } void AnimationWidget::slotUpdateCenters(const QVariantList ¢ers) { if (centers.count() != m_animController.key_count()) { qCDebug(KDENLIVE_LOG) << "* * * *CENTER POINTS MISMATCH, aborting edit"; } int pos; mlt_keyframe_type type; for (int i = 0; i < m_animController.key_count(); ++i) { m_animController.key_get(i, pos, type); mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), pos, m_outPoint); // Center rect to new pos QPointF offset = centers.at(i).toPointF() - QPointF(rect.x + rect.w / 2, rect.y + rect.h / 2); rect.x += offset.x(); rect.y += offset.y(); m_animProperties.anim_set(m_rectParameter.toUtf8().constData(), rect, pos, m_outPoint, type); } slotAdjustRectKeyframeValue(); } void AnimationWidget::setupMonitor(QRect r) { QVariantList points; QVariantList types; int pos; mlt_keyframe_type type; for (int j = 0; j < m_animController.key_count(); ++j) { m_animController.key_get(j, pos, type); if (m_animController.key_get_type(j) == mlt_keyframe_smooth) { types << 1; } else { types << 0; } mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), pos, m_outPoint); QRectF frameRect(rect.x, rect.y, rect.w, rect.h); points.append(QVariant(frameRect.center())); } if (m_active) { m_monitor->setUpEffectGeometry(r, points, types); } } void AnimationWidget::slotSeekToKeyframe(int ix) { int pos = m_animController.key_get_frame(ix); slotPositionChanged(pos, true); } void AnimationWidget::connectMonitor(bool activate) { if (m_active == activate) { return; } m_active = activate; if (!m_spinX) { // No animated rect displayed in monitor, return return; } if (activate) { connect(m_monitor, &Monitor::effectChanged, this, &AnimationWidget::slotUpdateGeometryRect, Qt::UniqueConnection); connect(m_monitor, &Monitor::effectPointsChanged, this, &AnimationWidget::slotUpdateCenters, Qt::UniqueConnection); connect(m_monitor, &Monitor::seekToKeyframe, this, &AnimationWidget::slotSeekToKeyframe, Qt::UniqueConnection); connect(m_monitor, &Monitor::seekToNextKeyframe, this, &AnimationWidget::slotNext, Qt::UniqueConnection); connect(m_monitor, &Monitor::seekToPreviousKeyframe, this, &AnimationWidget::slotPrevious, Qt::UniqueConnection); connect(m_monitor, SIGNAL(addKeyframe()), this, SLOT(slotAddKeyframe()), Qt::UniqueConnection); connect(m_monitor, SIGNAL(deleteKeyframe()), this, SLOT(slotDeleteKeyframe()), Qt::UniqueConnection); int framePos = qBound(0, m_monitor->position() - m_inPoint, m_timePos->maximum()); slotPositionChanged(framePos, false); double ratio = (double)m_spinWidth->value() / m_spinHeight->value(); if (m_frameSize.width() != m_monitorSize.width() || m_frameSize.height() != m_monitorSize.height()) { // Source frame size different than project frame size, enable original size option accordingly bool isOriginalSize = qAbs((double)m_frameSize.width() / m_frameSize.height() - ratio) < qAbs((double)m_monitorSize.width() / m_monitorSize.height() - ratio); if (isOriginalSize) { m_originalSize->blockSignals(true); m_originalSize->setChecked(true); m_originalSize->blockSignals(false); } } } else { disconnect(m_monitor, &Monitor::effectChanged, this, &AnimationWidget::slotUpdateGeometryRect); disconnect(m_monitor, &Monitor::effectPointsChanged, this, &AnimationWidget::slotUpdateCenters); disconnect(m_monitor, SIGNAL(addKeyframe()), this, SLOT(slotAddKeyframe())); disconnect(m_monitor, SIGNAL(deleteKeyframe()), this, SLOT(slotDeleteKeyframe())); disconnect(m_monitor, &Monitor::seekToNextKeyframe, this, &AnimationWidget::slotNext); disconnect(m_monitor, &Monitor::seekToPreviousKeyframe, this, &AnimationWidget::slotPrevious); disconnect(m_monitor, &Monitor::seekToKeyframe, this, &AnimationWidget::slotSeekToKeyframe); } } void AnimationWidget::offsetAnimation(int offset) { int pos = 0; mlt_keyframe_type type; QString offsetAnimation = QStringLiteral("kdenliveOffset"); if (m_spinX) { m_animController = m_animProperties.get_animation(m_rectParameter.toUtf8().constData()); for (int j = 0; j < m_animController.key_count(); ++j) { m_animController.key_get(j, pos, type); mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), pos, m_outPoint); m_animProperties.anim_set(offsetAnimation.toUtf8().constData(), rect, pos + offset, m_outPoint, type); } Mlt::Animation offsetAnim = m_animProperties.get_animation(offsetAnimation.toUtf8().constData()); m_animProperties.set(m_rectParameter.toUtf8().constData(), offsetAnim.serialize_cut()); // Required to initialize anim property m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), 0, m_outPoint); m_animProperties.set(offsetAnimation.toUtf8().constData(), ""); } QMapIterator i(m_doubleWidgets); while (i.hasNext()) { i.next(); m_animController = m_animProperties.get_animation(i.key().toUtf8().constData()); for (int j = 0; j < m_animController.key_count(); ++j) { m_animController.key_get(j, pos, type); double val = m_animProperties.anim_get_double(i.key().toUtf8().constData(), pos, m_outPoint); m_animProperties.anim_set(offsetAnimation.toUtf8().constData(), val, pos + offset, m_outPoint, type); } Mlt::Animation offsetAnim = m_animProperties.get_animation(offsetAnimation.toUtf8().constData()); m_animProperties.set(i.key().toUtf8().constData(), offsetAnim.serialize_cut()); // Required to initialize anim property m_animProperties.anim_get_int(i.key().toUtf8().constData(), 0, m_outPoint); m_animProperties.set(offsetAnimation.toUtf8().constData(), ""); } m_offset -= offset; } void AnimationWidget::reload(const QString &tag, const QString &data) { m_animProperties.set(tag.toUtf8().constData(), data.toUtf8().constData()); if (tag == m_rectParameter) { m_animProperties.anim_get_rect(tag.toUtf8().constData(), 0, m_outPoint); } else { m_animProperties.anim_get_int(tag.toUtf8().constData(), 0, m_outPoint); m_attachedToEnd = KeyframeView::checkNegatives(data, m_outPoint); m_inTimeline = tag; } // Also add keyframes positions in other parameters QStringList paramNames = m_doubleWidgets.keys(); QLocale locale; m_animController = m_animProperties.get_animation(tag.toUtf8().constData()); for (int i = 0; i < paramNames.count(); i++) { const QString ¤tParam = paramNames.at(i); if (currentParam == tag) { continue; } // simple anim parameter, get default value double def = m_model->data(m_index, AssetParameterModel::DefaultRole).toDouble(); // Clear current keyframes m_animProperties.set(currentParam.toUtf8().constData(), ""); // Add default keyframes int pos; mlt_keyframe_type type; for (int j = 0; j < m_animController.key_count(); ++j) { m_animController.key_get(j, pos, type); m_animProperties.anim_set(currentParam.toUtf8().constData(), def, pos, m_outPoint, type); } m_animProperties.anim_get_int(currentParam.toUtf8().constData(), 0, m_outPoint); } if (!m_rectParameter.isEmpty() && tag != m_rectParameter) { // reset geometry keyframes // simple anim parameter, get default value QString def = m_model->data(m_index, AssetParameterModel::DefaultRole).toString(); // Clear current keyframes m_animProperties.set(m_rectParameter.toUtf8().constData(), def.toUtf8().constData()); // Add default keyframes int pos; mlt_keyframe_type type; m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), 0, m_outPoint); mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), 0, m_outPoint); for (int j = 0; j < m_animController.key_count(); ++j) { m_animController.key_get(j, pos, type); m_animProperties.anim_set(m_rectParameter.toUtf8().constData(), rect, pos, m_outPoint, type); } m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), 0, m_outPoint); } rebuildKeyframes(); emit valueChanged(m_index, QString(m_animController.serialize_cut()), true); } QString AnimationWidget::defaultValue(const QString ¶mName) { QStringList paramNames = m_doubleWidgets.keys(); for (int i = 0; i < paramNames.count(); i++) { if (m_params.at(i).attribute(QStringLiteral("name")) == paramName) { QString def = m_params.at(i).attribute(QStringLiteral("default")); if (def.contains(QLatin1Char('%'))) { def = EffectsController::getStringRectEval(def); } return def; } } return QString(); } void AnimationWidget::slotAdjustToSource() { m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); m_spinWidth->setValue((int)(m_frameSize.width() / pCore->getCurrentSar() + 0.5), false); m_spinHeight->setValue(m_frameSize.height(), false); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); if (m_lockRatio->isChecked()) { m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), m_originalSize->isChecked() ? (double)m_frameSize.width() / m_frameSize.height() : (double)m_monitorSize.width() / m_monitorSize.height()); } } void AnimationWidget::slotAdjustToFrameSize() { double monitorDar = pCore->getCurrentDar(); double sourceDar = m_frameSize.width() / m_frameSize.height(); m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); if (sourceDar > monitorDar) { // Fit to width double factor = (double)m_monitorSize.width() / m_frameSize.width() * pCore->getCurrentSar(); m_spinHeight->setValue((int)(m_frameSize.height() * factor + 0.5)); m_spinWidth->setValue(m_monitorSize.width()); // Center m_spinY->blockSignals(true); m_spinY->setValue((m_monitorSize.height() - m_spinHeight->value()) / 2); m_spinY->blockSignals(false); } else { // Fit to height double factor = (double)m_monitorSize.height() / m_frameSize.height(); m_spinHeight->setValue(m_monitorSize.height()); m_spinWidth->setValue((int)(m_frameSize.width() / pCore->getCurrentSar() * factor + 0.5)); // Center m_spinX->blockSignals(true); m_spinX->setValue((m_monitorSize.width() - m_spinWidth->value()) / 2); m_spinX->blockSignals(false); } m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); } void AnimationWidget::slotFitToWidth() { double factor = (double)m_monitorSize.width() / m_frameSize.width() * pCore->getCurrentSar(); m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); m_spinHeight->setValue((int)(m_frameSize.height() * factor + 0.5)); m_spinWidth->setValue(m_monitorSize.width()); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); } void AnimationWidget::slotFitToHeight() { double factor = (double)m_monitorSize.height() / m_frameSize.height(); m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); m_spinHeight->setValue(m_monitorSize.height()); m_spinWidth->setValue((int)(m_frameSize.width() / pCore->getCurrentSar() * factor + 0.5)); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); } void AnimationWidget::slotMoveLeft() { m_spinX->setValue(0); } void AnimationWidget::slotCenterH() { m_spinX->setValue((m_monitorSize.width() - m_spinWidth->value()) / 2); } void AnimationWidget::slotMoveRight() { m_spinX->setValue(m_monitorSize.width() - m_spinWidth->value()); } void AnimationWidget::slotMoveTop() { m_spinY->setValue(0); } void AnimationWidget::slotCenterV() { m_spinY->setValue((m_monitorSize.height() - m_spinHeight->value()) / 2); } void AnimationWidget::slotMoveBottom() { m_spinY->setValue(m_monitorSize.height() - m_spinHeight->value()); } void AnimationWidget::slotCopyKeyframes() { const QMap anims = getAnimation(); if (anims.isEmpty()) { return; } QString result; QMapIterator i(anims); while (i.hasNext()) { i.next(); result.append(i.key() + QLatin1Char('=') + i.value() + QLatin1Char('\n')); } QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(result); } void AnimationWidget::slotImportKeyframes() { QClipboard *clipboard = QApplication::clipboard(); QString values = clipboard->text(); emit setKeyframes(values); } void AnimationWidget::slotLockRatio() { QAction *lockRatio = qobject_cast(QObject::sender()); if (lockRatio->isChecked()) { m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), m_originalSize->isChecked() ? (double)m_frameSize.width() / m_frameSize.height() : (double)m_monitorSize.width() / m_monitorSize.height()); } else { m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), -1); } } void AnimationWidget::slotAdjustRectWidth() { if (m_lockRatio->isChecked()) { m_spinHeight->blockSignals(true); if (m_originalSize->isChecked()) { m_spinHeight->setValue((int)(m_spinWidth->value() * m_frameSize.height() / m_frameSize.width() + 0.5)); } else { m_spinHeight->setValue((int)(m_spinWidth->value() * m_monitorSize.height() / m_monitorSize.width() + 0.5)); } m_spinHeight->blockSignals(false); } slotAdjustRectKeyframeValue(); } void AnimationWidget::slotAdjustRectHeight() { if (m_lockRatio->isChecked()) { m_spinWidth->blockSignals(true); if (m_originalSize->isChecked()) { m_spinWidth->setValue((int)(m_spinHeight->value() * m_frameSize.width() / m_frameSize.height() + 0.5)); } else { m_spinWidth->setValue((int)(m_spinHeight->value() * m_monitorSize.width() / m_monitorSize.height() + 0.5)); } m_spinWidth->blockSignals(false); } slotAdjustRectKeyframeValue(); } void AnimationWidget::monitorSeek(int pos) { slotPositionChanged(pos - m_inPoint, false); if (m_spinX) { // Update monitor scene for geometry params if (pos > m_inPoint && pos < m_outPoint) { if (!m_active) { connectMonitor(true); m_monitor->slotShowEffectScene(MonitorSceneGeometry); } } else { if (m_active) { connectMonitor(false); m_monitor->slotShowEffectScene(MonitorSceneDefault); } } } } void AnimationWidget::slotShowComment(bool show) {} void AnimationWidget::slotRefresh() { for (int i = 0; i < m_parameters.count(); i++) { QModelIndex ix = m_parameters.at(i).first; ParamType type = (ParamType)m_model->data(ix, AssetParameterModel::TypeRole).toInt(); QString keyframes = m_model->data(ix, AssetParameterModel::ValueRole).toString(); QString paramTag = m_model->data(ix, AssetParameterModel::NameRole).toString(); m_animProperties.set(paramTag.toUtf8().constData(), keyframes.toUtf8().constData()); m_attachedToEnd = KeyframeView::checkNegatives(keyframes, m_outPoint); if (type == ParamType::Animated || type == ParamType::RestrictedAnim) { // one dimension parameter // Required to initialize anim property m_animProperties.anim_get_int(paramTag.toUtf8().constData(), 0, m_outPoint); } else { // rect parameter // TODO: multiple rect parameters in effect ? m_rectParameter = paramTag; m_inTimeline = paramTag; // Required to initialize anim property m_animProperties.anim_get_rect(paramTag.toUtf8().constData(), 0, m_outPoint); } } rebuildKeyframes(); monitorSeek(m_monitor->position()); } void AnimationWidget::slotSetRange(QPair range) { m_inPoint = range.first; m_outPoint = range.second; m_offset = m_model->data(m_index, AssetParameterModel::InRole).toInt(); m_ruler->setRange(0, m_outPoint - m_inPoint); m_timePos->setRange(0, m_outPoint - m_inPoint - 1); } diff --git a/src/assets/view/widgets/doubleparamwidget.cpp b/src/assets/view/widgets/doubleparamwidget.cpp index 9653683ea..7faf211c0 100644 --- a/src/assets/view/widgets/doubleparamwidget.cpp +++ b/src/assets/view/widgets/doubleparamwidget.cpp @@ -1,69 +1,69 @@ /*************************************************************************** * Copyright (C) 2016 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 "doubleparamwidget.hpp" #include "assets/model/assetparametermodel.hpp" #include "widgets/doublewidget.h" #include #include DoubleParamWidget::DoubleParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) , m_doubleWidget(nullptr) { m_lay = new QVBoxLayout(this); m_lay->setContentsMargins(0, 0, 0, 0); m_lay->setSpacing(0); QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); // Retrieve parameters from the model QString name = m_model->data(m_index, Qt::DisplayRole).toString(); double value = locale.toDouble(m_model->data(m_index, AssetParameterModel::ValueRole).toString()); double min = m_model->data(m_index, AssetParameterModel::MinRole).toDouble(); double max = m_model->data(m_index, AssetParameterModel::MaxRole).toDouble(); double defaultValue = locale.toDouble(m_model->data(m_index, AssetParameterModel::DefaultRole).toString()); QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString(); QString suffix = m_model->data(m_index, AssetParameterModel::SuffixRole).toString(); int decimals = m_model->data(m_index, AssetParameterModel::DecimalsRole).toInt(); double factor = m_model->data(m_index, AssetParameterModel::FactorRole).toDouble(); // Construct object - m_doubleWidget = new DoubleWidget(name, value, min, max, factor, defaultValue, comment, -1, suffix, decimals, this); + m_doubleWidget = new DoubleWidget(name, value, min, max, factor, defaultValue, comment, -1, suffix, decimals, m_model->data(m_index, AssetParameterModel::OddRole).toBool(), this); m_lay->addWidget(m_doubleWidget); setMinimumHeight(m_doubleWidget->height()); // Connect signal connect(m_doubleWidget, &DoubleWidget::valueChanged, [this, locale](double val) { emit valueChanged(m_index, locale.toString(val), true); }); slotRefresh(); } void DoubleParamWidget::slotRefresh() { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); double value = locale.toDouble(m_model->data(m_index, AssetParameterModel::ValueRole).toString()); m_doubleWidget->setValue(value); } void DoubleParamWidget::slotShowComment(bool show) { m_doubleWidget->slotShowComment(show); } diff --git a/src/assets/view/widgets/keyframeedit.cpp b/src/assets/view/widgets/keyframeedit.cpp index cbfafd9e0..4c7ed2801 100644 --- a/src/assets/view/widgets/keyframeedit.cpp +++ b/src/assets/view/widgets/keyframeedit.cpp @@ -1,625 +1,625 @@ /*************************************************************************** keyframedit.cpp - description ------------------- begin : 03 Aug 2008 copyright : (C) 2008 by Marco Gittler email : g.marco@freenet.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "keyframeedit.h" #include "assets/model/assetparametermodel.hpp" #include "core.h" #include "kdenlivesettings.h" #include "monitor/monitormanager.h" #include "widgets/doublewidget.h" #include "widgets/positionwidget.h" #include "kdenlive_debug.h" #include "klocalizedstring.h" #include #include KeyframeEdit::KeyframeEdit(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) , m_keyframesTag(false) { m_min = m_model->data(m_index, AssetParameterModel::InRole).toInt(); m_max = m_model->data(m_index, AssetParameterModel::OutRole).toInt() + 1; // TODO: for compositions, offset = min but for clips it might differ m_offset = m_min; setupUi(this); if (m_max == -1) { // special case: keyframe for tracks, do not allow keyframes widgetTable->setHidden(true); } setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); keyframe_list->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); buttonSeek->setChecked(KdenliveSettings::keyframeseek()); connect(buttonSeek, &QAbstractButton::toggled, this, &KeyframeEdit::slotSetSeeking); buttonKeyframes->setIcon(QIcon::fromTheme(QStringLiteral("chronometer"))); button_add->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); button_add->setToolTip(i18n("Add keyframe")); button_delete->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); button_delete->setToolTip(i18n("Delete keyframe")); buttonResetKeyframe->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); buttonSeek->setIcon(QIcon::fromTheme(QStringLiteral("edit-link"))); connect(keyframe_list, &QTableWidget::currentCellChanged, this, &KeyframeEdit::rowClicked); connect(keyframe_list, &QTableWidget::cellChanged, this, &KeyframeEdit::slotGenerateParams); m_position = new PositionWidget(i18n("Position"), 0, 0, 1, pCore->monitorManager()->timecode(), QStringLiteral(""), widgetTable); ((QGridLayout *)widgetTable->layout())->addWidget(m_position, 3, 0, 1, -1); m_slidersLayout = new QGridLayout(param_sliders); // m_slidersLayout->setSpacing(0); m_slidersLayout->setContentsMargins(0, 0, 0, 0); m_slidersLayout->setVerticalSpacing(2); keyframe_list->setSelectionBehavior(QAbstractItemView::SelectRows); keyframe_list->setSelectionMode(QAbstractItemView::SingleSelection); addParameter(m_index); keyframe_list->resizeRowsToContents(); // keyframe_list->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); connect(button_delete, &QAbstractButton::clicked, this, &KeyframeEdit::slotDeleteKeyframe); connect(button_add, &QAbstractButton::clicked, this, &KeyframeEdit::slotAddKeyframe); connect(buttonKeyframes, &QAbstractButton::clicked, this, &KeyframeEdit::slotKeyframeMode); connect(buttonResetKeyframe, &QAbstractButton::clicked, this, &KeyframeEdit::slotResetKeyframe); connect(m_position, &PositionWidget::valueChanged, [&]() { slotAdjustKeyframePos(m_position->getPosition()); }); // connect(keyframe_list, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(slotSaveCurrentParam(QTreeWidgetItem*,int))); if (!keyframe_list->currentItem()) { keyframe_list->setCurrentCell(0, 0); keyframe_list->selectRow(0); } // ensure the keyframe list shows at least 3 lines keyframe_list->setMinimumHeight(QFontInfo(keyframe_list->font()).pixelSize() * 9); // Do not show keyframe table if only one keyframe exists at the beginning if (keyframe_list->rowCount() < 2 && getPos(0) == m_min && m_max != -1) { widgetTable->setHidden(true); } else { buttonKeyframes->setHidden(true); } } KeyframeEdit::~KeyframeEdit() { setEnabled(false); keyframe_list->blockSignals(true); keyframe_list->clear(); QLayoutItem *child; while ((child = m_slidersLayout->takeAt(0)) != nullptr) { QWidget *wid = child->widget(); delete child; delete wid; } } void KeyframeEdit::addParameter(QModelIndex index, int activeKeyframe) { keyframe_list->blockSignals(true); // Retrieve parameters from the model QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); QString name = m_model->data(index, Qt::DisplayRole).toString(); double value = 0; // locale.toDouble(m_model->data(index, AssetParameterModel::ValueRole).toString()); double min = m_model->data(index, AssetParameterModel::MinRole).toDouble(); double max = m_model->data(index, AssetParameterModel::MaxRole).toDouble(); double defaultValue = locale.toDouble(m_model->data(index, AssetParameterModel::DefaultRole).toString()); QString comment = m_model->data(index, AssetParameterModel::CommentRole).toString(); QString suffix = m_model->data(index, AssetParameterModel::SuffixRole).toString(); int decimals = m_model->data(index, AssetParameterModel::DecimalsRole).toInt(); int columnId = keyframe_list->columnCount(); m_paramIndexes << index; keyframe_list->insertColumn(columnId); keyframe_list->setHorizontalHeaderItem(columnId, new QTableWidgetItem(name)); DoubleWidget *doubleparam = new DoubleWidget(name, value, min, max, m_model->data(index, AssetParameterModel::FactorRole).toDouble(), defaultValue, comment, - -1, suffix, decimals, this); + -1, suffix, decimals, m_model->data(index, AssetParameterModel::OddRole).toBool(), this); /*DoubleParameterWidget *doubleparam = new DoubleParameterWidget( paramName, 0, m_params.at(columnId).attribute(QStringLiteral("min")).toDouble(), m_params.at(columnId).attribute(QStringLiteral("max")).toDouble(), m_params.at(columnId).attribute(QStringLiteral("default")).toDouble(), comment, columnId, m_params.at(columnId).attribute(QStringLiteral("suffix")), m_params.at(columnId).attribute(QStringLiteral("decimals")).toInt(), false, this);*/ // Connect signal // connect(doubleparam, &DoubleWidget::valueChanged, [this, locale, index](double value) { emit valueChanged(index, locale.toString(value)); }); connect(doubleparam, &DoubleWidget::valueChanged, this, &KeyframeEdit::slotAdjustKeyframeValue); connect(this, SIGNAL(showComments(bool)), doubleparam, SLOT(slotShowComment(bool))); // connect(doubleparam, SIGNAL(setInTimeline(int)), this, SLOT(slotUpdateVisibleParameter(int))); m_slidersLayout->addWidget(doubleparam, columnId, 0); /*if (e.attribute(QStringLiteral("intimeline")) == QLatin1String("1")) { doubleparam->setInTimelineProperty(true); }*/ QStringList frames; /*if (e.hasAttribute(QStringLiteral("keyframes"))) { // Effects have keyframes in a "keyframe" attribute, not sure why frames = e.attribute(QStringLiteral("keyframes")).split(QLatin1Char(';'), QString::SkipEmptyParts); m_keyframesTag = true; } else {*/ // Transitions take keyframes from the value param QString framesValue = m_model->data(index, AssetParameterModel::ValueRole).toString(); if (!framesValue.contains(QLatin1Char('='))) { framesValue.prepend(QStringLiteral("0=")); } frames = framesValue.split(QLatin1Char(';'), QString::SkipEmptyParts); m_keyframesTag = false; //} for (int i = 0; i < frames.count(); ++i) { int frame = frames.at(i).section(QLatin1Char('='), 0, 0).toInt(); bool found = false; int j; for (j = 0; j < keyframe_list->rowCount(); ++j) { int currentPos = getPos(j); if (frame == currentPos) { keyframe_list->setItem(j, columnId, new QTableWidgetItem(frames.at(i).section(QLatin1Char('='), 1, 1))); found = true; break; } else if (currentPos > frame) { break; } } if (!found) { keyframe_list->insertRow(j); keyframe_list->setVerticalHeaderItem(j, new QTableWidgetItem(getPosString(frame))); keyframe_list->setItem(j, columnId, new QTableWidgetItem(frames.at(i).section(QLatin1Char('='), 1, 1))); keyframe_list->resizeRowToContents(j); } if ((activeKeyframe > -1) && (activeKeyframe == frame)) { keyframe_list->setCurrentCell(i, columnId); keyframe_list->selectRow(i); } } if (!keyframe_list->currentItem()) { keyframe_list->setCurrentCell(0, columnId); keyframe_list->selectRow(0); } keyframe_list->resizeColumnsToContents(); keyframe_list->blockSignals(false); keyframe_list->horizontalHeader()->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); keyframe_list->verticalHeader()->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); slotAdjustKeyframeInfo(false); button_delete->setEnabled(keyframe_list->rowCount() > 1); } void KeyframeEdit::slotDeleteKeyframe() { if (keyframe_list->rowCount() < 2) { return; } int col = keyframe_list->currentColumn(); int row = keyframe_list->currentRow(); keyframe_list->removeRow(keyframe_list->currentRow()); row = qMin(row, keyframe_list->rowCount() - 1); keyframe_list->setCurrentCell(row, col); keyframe_list->selectRow(row); generateAllParams(); bool disable = keyframe_list->rowCount() < 2; button_delete->setEnabled(!disable); disable &= static_cast(getPos(0) == m_min); widgetTable->setHidden(disable); buttonKeyframes->setHidden(!disable); } void KeyframeEdit::slotAddKeyframe(int pos) { keyframe_list->blockSignals(true); int row = 0; int col = keyframe_list->currentColumn(); int newrow = row; int result = pos; if (result > 0) { // A position was provided for (; newrow < keyframe_list->rowCount(); newrow++) { int rowPos = getPos(newrow); if (rowPos > result) { break; } } } else { row = keyframe_list->currentRow(); newrow = row; int pos1 = getPos(row); if (row < (keyframe_list->rowCount() - 1)) { result = pos1 + (getPos(row + 1) - pos1) / 2; newrow++; } else if (row == 0) { if (pos1 == m_min) { result = m_max; newrow++; } else { result = m_min; } } else { if (pos1 < m_max) { // last keyframe selected and it is not at end of clip -> add keyframe at the end result = m_max; newrow++; } else { int pos2 = getPos(row - 1); result = pos2 + (pos1 - pos2) / 2; } } } // Calculate new values QList previousValues; QList nextValues; int rowCount = keyframe_list->rowCount(); // Insert new row keyframe_list->insertRow(newrow); keyframe_list->setVerticalHeaderItem(newrow, new QTableWidgetItem(getPosString(result))); // If keyframe is inserted at start if (newrow == 0) { for (int i = 0; i < keyframe_list->columnCount(); ++i) { int newValue = keyframe_list->item(1, i)->text().toDouble(); keyframe_list->setItem(newrow, i, new QTableWidgetItem(QString::number(newValue))); } } else if (newrow == rowCount) { // Keyframe inserted at end for (int i = 0; i < keyframe_list->columnCount(); ++i) { int newValue = keyframe_list->item(rowCount - 1, i)->text().toDouble(); keyframe_list->setItem(newrow, i, new QTableWidgetItem(QString::number(newValue))); } } else { // Interpolate int previousPos = getPos(newrow - 1); int nextPos = getPos(newrow + 1); if (nextPos > previousPos) { double factor = ((double)result - previousPos) / (nextPos - previousPos); for (int i = 0; i < keyframe_list->columnCount(); ++i) { double previousValues = keyframe_list->item(newrow - 1, i)->text().toDouble(); double nextValues = keyframe_list->item(newrow + 1, i)->text().toDouble(); int newValue = (int)(previousValues + (nextValues - previousValues) * factor); keyframe_list->setItem(newrow, i, new QTableWidgetItem(QString::number(newValue))); } } } keyframe_list->resizeRowsToContents(); keyframe_list->setCurrentCell(newrow, col); slotAdjustKeyframeInfo(); keyframe_list->blockSignals(false); generateAllParams(); if (rowCount == 1) { // there was only one keyframe before, so now enable keyframe mode slotKeyframeMode(); } button_delete->setEnabled(keyframe_list->rowCount() > 1); } void KeyframeEdit::slotGenerateParams(int row, int column) { if (column == -1) { // position of keyframe changed QTableWidgetItem *item = keyframe_list->item(row, 0); if (item == nullptr) { return; } int pos = getPos(row) + m_offset; if (pos <= m_min) { pos = m_min; } if (m_max != -1 && pos > m_max) { pos = m_max; } QString val = getPosString(pos - m_offset); if (val != keyframe_list->verticalHeaderItem(row)->text()) { keyframe_list->verticalHeaderItem(row)->setText(val); } for (int col = 0; col < keyframe_list->horizontalHeader()->count(); ++col) { item = keyframe_list->item(row, col); if (!item) { continue; } int min = m_model->data(m_paramIndexes.at(col), AssetParameterModel::MinRole).toInt(); int max = m_model->data(m_paramIndexes.at(col), AssetParameterModel::MaxRole).toInt(); int v = item->text().toInt(); int result = qBound(min, v, max); if (v != result) { item->setText(QString::number(result)); } QString keyframes; for (int i = 0; i < keyframe_list->rowCount(); ++i) { if (keyframe_list->item(i, col)) { keyframes.append(QString::number(getPos(i)) + QLatin1Char('=') + keyframe_list->item(i, col)->text() + QLatin1Char(';')); } } emit valueChanged(m_paramIndexes.at(col), keyframes, true); // m_params[col].setAttribute(getTag(), keyframes); } return; } QTableWidgetItem *item = keyframe_list->item(row, column); if (item == nullptr) { return; } int pos = getPos(row) + m_offset; if (pos <= m_min) { pos = m_min; } if (m_max != -1 && pos > m_max) { pos = m_max; } /*QList duplicates = keyframe_list->findItems(val, Qt::MatchExactly, 0); duplicates.removeAll(item); if (!duplicates.isEmpty()) { // Trying to insert a keyframe at existing value, revert it val = m_timecode.getTimecodeFromFrames(m_previousPos); }*/ QString val = getPosString(pos - m_offset); if (val != keyframe_list->verticalHeaderItem(row)->text()) { keyframe_list->verticalHeaderItem(row)->setText(val); } int min = m_model->data(m_paramIndexes.at(column), AssetParameterModel::MinRole).toInt(); int max = m_model->data(m_paramIndexes.at(column), AssetParameterModel::MaxRole).toInt(); int v = item->text().toInt(); int result = qBound(min, v, max); if (v != result) { item->setText(QString::number(result)); } slotAdjustKeyframeInfo(false); QString keyframes; for (int i = 0; i < keyframe_list->rowCount(); ++i) { if (keyframe_list->item(i, column)) { keyframes.append(QString::number(getPos(i)) + QLatin1Char('=') + keyframe_list->item(i, column)->text() + QLatin1Char(';')); } } qDebug() << "/// ADJUSTING PARAM: " << column << " = " << keyframes; emit valueChanged(m_paramIndexes.at(column), keyframes, true); } const QString KeyframeEdit::getTag() const { QString tag = m_keyframesTag ? QStringLiteral("keyframes") : QStringLiteral("value"); return tag; } void KeyframeEdit::generateAllParams() { for (int col = 0; col < keyframe_list->columnCount(); ++col) { QString keyframes; for (int i = 0; i < keyframe_list->rowCount(); ++i) { if (keyframe_list->item(i, col)) { keyframes.append(QString::number(getPos(i)) + QLatin1Char('=') + keyframe_list->item(i, col)->text() + QLatin1Char(';')); } } emit valueChanged(m_paramIndexes.at(col), keyframes, true); } } const QString KeyframeEdit::getValue(const QString &name) { for (int col = 0; col < keyframe_list->columnCount(); ++col) { QDomNode na = m_params.at(col).firstChildElement(QStringLiteral("name")); QString paramName = i18n(na.toElement().text().toUtf8().data()); if (paramName == name) { return m_params.at(col).attribute(getTag()); } } return QString(); } void KeyframeEdit::slotAdjustKeyframeInfo(bool seek) { QTableWidgetItem *item = keyframe_list->currentItem(); if (!item) { return; } int min = m_min - m_offset; int max = m_max - m_offset; QTableWidgetItem *above = keyframe_list->item(item->row() - 1, item->column()); QTableWidgetItem *below = keyframe_list->item(item->row() + 1, item->column()); if (above) { min = getPos(above->row()) + 1; } if (below) { max = getPos(below->row()) - 1; } m_position->blockSignals(true); m_position->setRange(min, max, true); m_position->setPosition(getPos(item->row())); m_position->blockSignals(false); QLocale locale; for (int col = 0; col < keyframe_list->columnCount(); ++col) { DoubleWidget *doubleparam = static_cast(m_slidersLayout->itemAtPosition(col, 0)->widget()); if (!doubleparam) { continue; } doubleparam->blockSignals(true); if (keyframe_list->item(item->row(), col)) { doubleparam->setValue(locale.toDouble(keyframe_list->item(item->row(), col)->text())); } else { // qCDebug(KDENLIVE_LOG) << "Null pointer exception caught: http://www.kdenlive.org/mantis/view.php?id=1771"; } doubleparam->blockSignals(false); } if (KdenliveSettings::keyframeseek() && seek) { emit seekToPos(m_position->getPosition() - m_min); } } void KeyframeEdit::slotAdjustKeyframePos(int value) { QTableWidgetItem *item = keyframe_list->currentItem(); keyframe_list->verticalHeaderItem(item->row())->setText(getPosString(value)); slotGenerateParams(item->row(), -1); if (KdenliveSettings::keyframeseek()) { emit seekToPos(value - m_min); } } void KeyframeEdit::slotAdjustKeyframeValue(double value) { Q_UNUSED(value) QTableWidgetItem *item = keyframe_list->currentItem(); for (int col = 0; col < keyframe_list->columnCount(); ++col) { DoubleWidget *doubleparam = static_cast(m_slidersLayout->itemAtPosition(col, 0)->widget()); if (!doubleparam) { continue; } double val = doubleparam->getValue(); QTableWidgetItem *nitem = keyframe_list->item(item->row(), col); if ((nitem != nullptr) && nitem->text().toDouble() != val) { nitem->setText(QString::number(val)); } } // keyframe_list->item(item->row() - 1, item->column()); } int KeyframeEdit::getPos(int row) { if (!keyframe_list->verticalHeaderItem(row)) { return 0; } if (KdenliveSettings::frametimecode()) { return keyframe_list->verticalHeaderItem(row)->text().toInt(); } return pCore->monitorManager()->timecode().getFrameCount(keyframe_list->verticalHeaderItem(row)->text()); } QString KeyframeEdit::getPosString(int pos) { if (KdenliveSettings::frametimecode()) { return QString::number(pos); } return pCore->monitorManager()->timecode().getTimecodeFromFrames(pos); } void KeyframeEdit::slotSetSeeking(bool seek) { KdenliveSettings::setKeyframeseek(seek); } void KeyframeEdit::updateTimecodeFormat() { for (int row = 0; row < keyframe_list->rowCount(); ++row) { QString pos = keyframe_list->verticalHeaderItem(row)->text(); if (KdenliveSettings::frametimecode()) { keyframe_list->verticalHeaderItem(row)->setText(QString::number(pCore->monitorManager()->timecode().getFrameCount(pos))); } else { keyframe_list->verticalHeaderItem(row)->setText(pCore->monitorManager()->timecode().getTimecodeFromFrames(pos.toInt())); } } m_position->updateTimecodeFormat(); } void KeyframeEdit::slotKeyframeMode() { widgetTable->setHidden(false); buttonKeyframes->setHidden(true); if (keyframe_list->rowCount() == 1) { slotAddKeyframe(); } } void KeyframeEdit::slotResetKeyframe() { for (int col = 0; col < keyframe_list->columnCount(); ++col) { DoubleWidget *doubleparam = static_cast(m_slidersLayout->itemAtPosition(col, 0)->widget()); if (doubleparam) { doubleparam->slotReset(); } } } void KeyframeEdit::slotUpdateVisibleParameter(int id, bool update) { for (int i = 0; i < m_params.count(); ++i) { m_params[i].setAttribute(QStringLiteral("intimeline"), (i == id ? "1" : "0")); } for (int col = 0; col < keyframe_list->columnCount(); ++col) { DoubleWidget *doubleparam = static_cast(m_slidersLayout->itemAtPosition(col, 0)->widget()); if (!doubleparam) { continue; } // TODO // doubleparam->setInTimelineProperty(col == id); ////qCDebug(KDENLIVE_LOG)<<"// PARAM: "<columnCount(); ++col) { QDomNode na = m_params.at(col).firstChildElement(QStringLiteral("name")); QString paramName = i18n(na.toElement().text().toUtf8().data()); if (paramName == name) { return m_params.at(col).attribute(QStringLiteral("intimeline")) == QLatin1String("1"); } } return false; } void KeyframeEdit::checkVisibleParam() { if (m_params.isEmpty()) { return; } for (const QDomElement &elem : m_params) { if (elem.attribute(QStringLiteral("intimeline")) == QLatin1String("1")) { return; } } slotUpdateVisibleParameter(0); } void KeyframeEdit::slotUpdateRange(int inPoint, int outPoint) { m_min = inPoint; m_max = outPoint - 1; } void KeyframeEdit::rowClicked(int newRow, int, int oldRow, int) { if (oldRow != newRow) { slotAdjustKeyframeInfo(true); } } void KeyframeEdit::slotShowComment(bool show) { emit showComments(show); } void KeyframeEdit::slotRefresh() { // TODO } void KeyframeEdit::slotSetRange(QPair range) { m_min = range.first; m_max = range.second; } diff --git a/src/assets/view/widgets/keyframewidget.cpp b/src/assets/view/widgets/keyframewidget.cpp index c9be7b067..1d56b187b 100644 --- a/src/assets/view/widgets/keyframewidget.cpp +++ b/src/assets/view/widgets/keyframewidget.cpp @@ -1,499 +1,499 @@ /*************************************************************************** * 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 KeyframeWidget::KeyframeWidget(std::shared_ptr model, QModelIndex index, QSize frameSize, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) , m_monitorHelper(nullptr) , m_neededScene(MonitorSceneType::MonitorSceneDefault) , m_sourceFrameSize(frameSize.isValid() && !frameSize.isNull() ? frameSize : pCore->getCurrentFrameSize()) , m_baseHeight(0) , m_addedHeight(0) { setSizePolicy(QSizePolicy::Preferred, 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); m_baseHeight = m_keyframeview->minimumHeight() + m_toolbar->sizeHint().height() + 2; setFixedHeight(m_baseHeight); 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()); 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, m_sourceFrameSize, 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); + auto doubleWidget = new DoubleWidget(name, value, min, max, factor, defaultValue, comment, -1, suffix, decimals, m_model->data(index, AssetParameterModel::OddRole).toBool(), 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); m_addedHeight += paramWidget->minimumHeight(); setFixedHeight(m_baseHeight + m_addedHeight); } } 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) { 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) { if (enable && m_toolbar->isVisible()) { return; } m_toolbar->setVisible(enable); m_keyframeview->setVisible(enable); setFixedHeight(m_addedHeight + (enable ? m_baseHeight : 0)); } 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() { int pos = m_time->getValue() + m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(); m_keyframes->removeNextKeyframes(GenTime(pos, pCore->getCurrentFps())); } diff --git a/src/widgets/doublewidget.cpp b/src/widgets/doublewidget.cpp index af50ff960..4865e9958 100644 --- a/src/widgets/doublewidget.cpp +++ b/src/widgets/doublewidget.cpp @@ -1,98 +1,98 @@ /************************************************************************** * Copyright (C) 2010 by Till Theato (root@ttill.de) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "doublewidget.h" #include "dragvalue.h" #include DoubleWidget::DoubleWidget(const QString &name, double value, double min, double max, double factor, double defaultValue, const QString &comment, int id, - const QString &suffix, int decimals, QWidget *parent) + const QString &suffix, int decimals, bool oddOnly, QWidget *parent) : QWidget(parent) , m_factor(factor) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); auto *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); - m_dragVal = new DragValue(name, defaultValue * m_factor, decimals, min, max, id, suffix, true, this); + m_dragVal = new DragValue(name, defaultValue * m_factor, decimals, min, max, id, suffix, true, oddOnly, this); layout->addWidget(m_dragVal); setMinimumHeight(m_dragVal->height()); if (!comment.isEmpty()) { setToolTip(comment); } m_dragVal->setValue(value * factor, false); connect(m_dragVal, &DragValue::valueChanged, this, &DoubleWidget::slotSetValue); } bool DoubleWidget::hasEditFocus() const { return m_dragVal->hasEditFocus(); } DoubleWidget::~DoubleWidget() { delete m_dragVal; } int DoubleWidget::spinSize() { return m_dragVal->spinSize(); } void DoubleWidget::setSpinSize(int width) { m_dragVal->setSpinSize(width); } void DoubleWidget::setValue(double value) { m_dragVal->blockSignals(true); m_dragVal->setValue(value * m_factor); m_dragVal->blockSignals(false); } void DoubleWidget::enableEdit(bool enable) { m_dragVal->setEnabled(enable); } void DoubleWidget::slotSetValue(double value, bool final) { if (final) { emit valueChanged(value / m_factor); } } double DoubleWidget::getValue() { return m_dragVal->value(); } void DoubleWidget::slotReset() { m_dragVal->slotReset(); } void DoubleWidget::slotShowComment(bool show) { Q_UNUSED(show) } diff --git a/src/widgets/doublewidget.h b/src/widgets/doublewidget.h index 3cc3d0b75..f2b063ddc 100644 --- a/src/widgets/doublewidget.h +++ b/src/widgets/doublewidget.h @@ -1,87 +1,87 @@ /*************************************************************************** * Copyright (C) 2010 by Till Theato (root@ttill.de) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #ifndef DOUBLEWIDGET_H #define DOUBLEWIDGET_H #include class DragValue; /** * @class DoubleWidget * @brief Widget to choose a double parameter (for a effect) with the help of a slider and a spinbox. * @author Till Theato * * The widget does only handle integers, so the parameter has to be converted into the proper double range afterwards. */ class DoubleWidget : public QWidget { Q_OBJECT public: /** @brief Sets up the parameter's GUI. * @param name Name of the parameter * @param value Value of the parameter * @param min Minimum value * @param max maximum value * @param factor value, if we want a range 0-1000 for a 0-1 parameter, we can set a factor of 1000 * @param defaultValue Value used when using reset functionality * @param comment A comment explaining the parameter. Will be shown in a tooltip. * @param suffix (optional) Suffix to display in spinbox * @param parent (optional) Parent Widget */ explicit DoubleWidget(const QString &name, double value, double min, double max, double factor, double defaultValue, const QString &comment, int id, - const QString &suffix = QString(), int decimals = 0, QWidget *parent = nullptr); + const QString &suffix = QString(), int decimals = 0, bool oddOnly = false, QWidget *parent = nullptr); ~DoubleWidget() override; /** @brief Gets the parameter's value. */ double getValue(); /** @brief Returns minimum size for QSpinBox, used to set all spinboxes to the same width. */ int spinSize(); void setSpinSize(int width); void enableEdit(bool enable); /** @brief Returns true if widget is currently being edited */ bool hasEditFocus() const; public slots: /** @brief Sets the value to @param value. */ void setValue(double value); /** @brief Sets value to m_default. */ void slotReset(); /** @brief Shows/Hides the comment label. */ void slotShowComment(bool show); private slots: void slotSetValue(double value, bool final); private: DragValue *m_dragVal; double m_factor; signals: void valueChanged(double); // same signal as valueChanged, but add an extra boolean to tell if user is dragging value or not void valueChanging(double, bool); }; #endif diff --git a/src/widgets/dragvalue.cpp b/src/widgets/dragvalue.cpp index 3b2f27620..cc66e9553 100644 --- a/src/widgets/dragvalue.cpp +++ b/src/widgets/dragvalue.cpp @@ -1,549 +1,565 @@ /*************************************************************************** * Copyright (C) 2011 by Till Theato (root@ttill.de) * * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * 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 "dragvalue.h" #include "kdenlivesettings.h" #include #include #include #include #include #include #include #include #include #include #include -DragValue::DragValue(const QString &label, double defaultValue, int decimals, double min, double max, int id, const QString &suffix, bool showSlider, +DragValue::DragValue(const QString &label, double defaultValue, int decimals, double min, double max, int id, const QString &suffix, bool showSlider, bool oddOnly, QWidget *parent) : QWidget(parent) , m_maximum(max) , m_minimum(min) , m_decimals(decimals) , m_default(defaultValue) , m_id(id) , m_intEdit(nullptr) , m_doubleEdit(nullptr) { if (showSlider) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); } else { setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding); } setFocusPolicy(Qt::StrongFocus); setContextMenuPolicy(Qt::CustomContextMenu); setFocusPolicy(Qt::StrongFocus); auto *l = new QHBoxLayout; l->setSpacing(0); l->setContentsMargins(0, 0, 0, 0); m_label = new CustomLabel(label, showSlider, m_maximum - m_minimum, this); l->addWidget(m_label); setMinimumHeight(m_label->sizeHint().height()); if (decimals == 0) { m_label->setMaximum(max - min); - m_label->setStep(1); + m_label->setStep(oddOnly ? 2 : 1); m_intEdit = new QSpinBox(this); m_intEdit->setObjectName(QStringLiteral("dragBox")); m_intEdit->setFocusPolicy(Qt::StrongFocus); if (!suffix.isEmpty()) { m_intEdit->setSuffix(suffix); } m_intEdit->setKeyboardTracking(false); m_intEdit->setButtonSymbols(QAbstractSpinBox::NoButtons); m_intEdit->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_intEdit->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); m_intEdit->setRange((int)m_minimum, (int)m_maximum); m_intEdit->setValue((int)m_default); + if (oddOnly) { + m_intEdit->setSingleStep(2); + } l->addWidget(m_intEdit); connect(m_intEdit, static_cast(&QSpinBox::valueChanged), this, static_cast(&DragValue::slotSetValue)); connect(m_intEdit, &QAbstractSpinBox::editingFinished, this, &DragValue::slotEditingFinished); } else { m_doubleEdit = new QDoubleSpinBox(this); m_doubleEdit->setDecimals(decimals); m_doubleEdit->setFocusPolicy(Qt::StrongFocus); m_doubleEdit->setObjectName(QStringLiteral("dragBox")); if (!suffix.isEmpty()) { m_doubleEdit->setSuffix(suffix); } m_doubleEdit->setKeyboardTracking(false); m_doubleEdit->setButtonSymbols(QAbstractSpinBox::NoButtons); m_doubleEdit->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_doubleEdit->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); m_doubleEdit->setRange(m_minimum, m_maximum); double factor = 100; if (m_maximum - m_minimum > 10000) { factor = 1000; } m_label->setStep(1); m_doubleEdit->setSingleStep((m_maximum - m_minimum) / factor); l->addWidget(m_doubleEdit); m_doubleEdit->setValue(m_default); connect(m_doubleEdit, SIGNAL(valueChanged(double)), this, SLOT(slotSetValue(double))); connect(m_doubleEdit, &QAbstractSpinBox::editingFinished, this, &DragValue::slotEditingFinished); } connect(m_label, SIGNAL(valueChanged(double, bool)), this, SLOT(setValueFromProgress(double, bool))); connect(m_label, &CustomLabel::resetValue, this, &DragValue::slotReset); setLayout(l); if (m_intEdit) { m_label->setMaximumHeight(m_intEdit->sizeHint().height()); } else { m_label->setMaximumHeight(m_doubleEdit->sizeHint().height()); } m_menu = new QMenu(this); m_scale = new KSelectAction(i18n("Scaling"), this); m_scale->addAction(i18n("Normal scale")); m_scale->addAction(i18n("Pixel scale")); m_scale->addAction(i18n("Nonlinear scale")); m_scale->setCurrentItem(KdenliveSettings::dragvalue_mode()); m_menu->addAction(m_scale); m_directUpdate = new QAction(i18n("Direct update"), this); m_directUpdate->setCheckable(true); m_directUpdate->setChecked(KdenliveSettings::dragvalue_directupdate()); m_menu->addAction(m_directUpdate); QAction *reset = new QAction(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Reset value"), this); connect(reset, &QAction::triggered, this, &DragValue::slotReset); m_menu->addAction(reset); if (m_id > -1) { QAction *timeline = new QAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18n("Show %1 in timeline", label), this); connect(timeline, &QAction::triggered, this, &DragValue::slotSetInTimeline); connect(m_label, &CustomLabel::setInTimeline, this, &DragValue::slotSetInTimeline); m_menu->addAction(timeline); } connect(this, &QWidget::customContextMenuRequested, this, &DragValue::slotShowContextMenu); connect(m_scale, static_cast(&KSelectAction::triggered), this, &DragValue::slotSetScaleMode); connect(m_directUpdate, &QAction::triggered, this, &DragValue::slotSetDirectUpdate); } DragValue::~DragValue() { delete m_intEdit; delete m_doubleEdit; delete m_menu; delete m_label; // delete m_scale; // delete m_directUpdate; } bool DragValue::hasEditFocus() const { QWidget *fWidget = QApplication::focusWidget(); return ((fWidget != nullptr) && fWidget->parentWidget() == this); } int DragValue::spinSize() { if (m_intEdit) { return m_intEdit->sizeHint().width(); } return m_doubleEdit->sizeHint().width(); } void DragValue::setSpinSize(int width) { if (m_intEdit) { m_intEdit->setMinimumWidth(width); } else { m_doubleEdit->setMinimumWidth(width); } } void DragValue::slotSetInTimeline() { emit inTimeline(m_id); } int DragValue::precision() const { return m_decimals; } qreal DragValue::maximum() const { return m_maximum; } qreal DragValue::minimum() const { return m_minimum; } qreal DragValue::value() const { if (m_intEdit) { return m_intEdit->value(); } return m_doubleEdit->value(); } void DragValue::setMaximum(qreal max) { if (!qFuzzyCompare(m_maximum, max)) { m_maximum = max; if (m_intEdit) { m_intEdit->setRange(m_minimum, m_maximum); } else { m_doubleEdit->setRange(m_minimum, m_maximum); } } } void DragValue::setMinimum(qreal min) { if (!qFuzzyCompare(m_minimum, min)) { m_minimum = min; if (m_intEdit) { m_intEdit->setRange(m_minimum, m_maximum); } else { m_doubleEdit->setRange(m_minimum, m_maximum); } } } void DragValue::setRange(qreal min, qreal max) { m_maximum = max; m_minimum = min; if (m_intEdit) { m_intEdit->setRange(m_minimum, m_maximum); } else { m_doubleEdit->setRange(m_minimum, m_maximum); } } void DragValue::setStep(qreal step) { if (m_intEdit) { m_intEdit->setSingleStep(step); } else { m_doubleEdit->setSingleStep(step); } } void DragValue::slotReset() { if (m_intEdit) { m_intEdit->blockSignals(true); m_intEdit->setValue(m_default); m_intEdit->blockSignals(false); emit valueChanged((int)m_default, true); } else { m_doubleEdit->blockSignals(true); m_doubleEdit->setValue(m_default); m_doubleEdit->blockSignals(false); emit valueChanged(m_default, true); } m_label->setProgressValue((m_default - m_minimum) / (m_maximum - m_minimum) * m_label->maximum()); } void DragValue::slotSetValue(int value) { setValue(value, true); } void DragValue::slotSetValue(double value) { setValue(value, true); } void DragValue::setValueFromProgress(double value, bool final) { value = m_minimum + value * (m_maximum - m_minimum) / m_label->maximum(); if (m_decimals == 0) { setValue(qRound(value), final); } else { setValue(value, final); } } void DragValue::setValue(double value, bool final) { value = qBound(m_minimum, value, m_maximum); m_label->setProgressValue((value - m_minimum) / (m_maximum - m_minimum) * m_label->maximum()); if (m_intEdit) { m_intEdit->blockSignals(true); m_intEdit->setValue((int)value); m_intEdit->blockSignals(false); emit valueChanged((int)value, final); } else { m_doubleEdit->blockSignals(true); m_doubleEdit->setValue(value); m_doubleEdit->blockSignals(false); emit valueChanged(value, final); } } void DragValue::focusOutEvent(QFocusEvent *) { if (m_intEdit) { m_intEdit->setFocusPolicy(Qt::StrongFocus); } else { m_doubleEdit->setFocusPolicy(Qt::StrongFocus); } } void DragValue::focusInEvent(QFocusEvent *e) { if (m_intEdit) { m_intEdit->setFocusPolicy(Qt::WheelFocus); } else { m_doubleEdit->setFocusPolicy(Qt::WheelFocus); } if (e->reason() == Qt::TabFocusReason || e->reason() == Qt::BacktabFocusReason) { if (m_intEdit) { m_intEdit->setFocus(e->reason()); } else { m_doubleEdit->setFocus(e->reason()); } } else { QWidget::focusInEvent(e); } } void DragValue::slotEditingFinished() { if (m_intEdit) { int value = m_intEdit->value(); m_intEdit->blockSignals(true); m_intEdit->clearFocus(); m_intEdit->blockSignals(false); if (!KdenliveSettings::dragvalue_directupdate()) { emit valueChanged((double)value, true); } } else { double value = m_doubleEdit->value(); m_doubleEdit->blockSignals(true); m_doubleEdit->clearFocus(); m_doubleEdit->blockSignals(false); if (!KdenliveSettings::dragvalue_directupdate()) { emit valueChanged(value, true); } } } void DragValue::slotShowContextMenu(const QPoint &pos) { // values might have been changed by another object of this class m_scale->setCurrentItem(KdenliveSettings::dragvalue_mode()); m_directUpdate->setChecked(KdenliveSettings::dragvalue_directupdate()); m_menu->exec(mapToGlobal(pos)); } void DragValue::slotSetScaleMode(int mode) { KdenliveSettings::setDragvalue_mode(mode); } void DragValue::slotSetDirectUpdate(bool directUpdate) { KdenliveSettings::setDragvalue_directupdate(directUpdate); } void DragValue::setInTimelineProperty(bool intimeline) { if (m_label->property("inTimeline").toBool() == intimeline) { return; } m_label->setProperty("inTimeline", intimeline); style()->unpolish(m_label); style()->polish(m_label); m_label->update(); if (m_intEdit) { m_intEdit->setProperty("inTimeline", intimeline); style()->unpolish(m_intEdit); style()->polish(m_intEdit); m_intEdit->update(); } else { m_doubleEdit->setProperty("inTimeline", intimeline); style()->unpolish(m_doubleEdit); style()->polish(m_doubleEdit); m_doubleEdit->update(); } } CustomLabel::CustomLabel(const QString &label, bool showSlider, int range, QWidget *parent) : QProgressBar(parent) , m_dragMode(false) , m_showSlider(showSlider) , m_step(10.0) // m_precision(pow(10, precision)), { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); setFormat(QLatin1Char(' ') + label); setFocusPolicy(Qt::StrongFocus); setCursor(Qt::PointingHandCursor); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); if (showSlider) { setRange(0, 1000); } else { setRange(0, range); QSize sh; const QFontMetrics &fm = fontMetrics(); sh.setWidth(fm.horizontalAdvance(QLatin1Char(' ') + label + QLatin1Char(' '))); setMaximumWidth(sh.width()); setObjectName(QStringLiteral("dragOnly")); } setValue(0); } void CustomLabel::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { m_dragStartPosition = m_dragLastPosition = e->pos(); e->accept(); } else if (e->button() == Qt::MidButton) { emit resetValue(); m_dragStartPosition = QPoint(-1, -1); } else { QWidget::mousePressEvent(e); } } void CustomLabel::mouseMoveEvent(QMouseEvent *e) { if (m_dragStartPosition != QPoint(-1, -1)) { if (!m_dragMode && (e->pos() - m_dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { m_dragMode = true; m_dragLastPosition = e->pos(); e->accept(); return; } if (m_dragMode) { if (KdenliveSettings::dragvalue_mode() > 0 || !m_showSlider) { int diff = e->x() - m_dragLastPosition.x(); if (e->modifiers() == Qt::ControlModifier) { diff *= 2; } else if (e->modifiers() == Qt::ShiftModifier) { diff /= 2; } if (KdenliveSettings::dragvalue_mode() == 2) { diff = (diff > 0 ? 1 : -1) * pow(diff, 2); } double nv = value() + diff * m_step; if (!qFuzzyCompare(nv, value())) { setNewValue(nv, KdenliveSettings::dragvalue_directupdate()); } } else { double nv = minimum() + ((double)maximum() - minimum()) / width() * e->pos().x(); if (!qFuzzyCompare(nv, value())) { - setNewValue(nv, KdenliveSettings::dragvalue_directupdate()); + if (m_step > 1) { + int current = (int) value(); + int diff = (nv - current) / m_step; + setNewValue(current + diff * m_step, true); + } else { + setNewValue(nv, KdenliveSettings::dragvalue_directupdate()); + } } } m_dragLastPosition = e->pos(); e->accept(); } } else { QWidget::mouseMoveEvent(e); } } void CustomLabel::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::MidButton) { e->accept(); return; } if (e->modifiers() == Qt::ControlModifier) { emit setInTimeline(); e->accept(); return; } if (m_dragMode) { setNewValue(value(), true); m_dragLastPosition = m_dragStartPosition; e->accept(); } else if (m_showSlider) { - setNewValue((double)maximum() * e->pos().x() / width(), true); + if (m_step > 1) { + int current = (int) value(); + int newVal = (double)maximum() * e->pos().x() / width(); + int diff = (newVal - current) / m_step; + setNewValue(current + diff * m_step, true); + } else { + setNewValue((double)maximum() * e->pos().x() / width(), true); + } m_dragLastPosition = m_dragStartPosition; e->accept(); } m_dragMode = false; } void CustomLabel::wheelEvent(QWheelEvent *e) { if (e->delta() > 0) { if (e->modifiers() == Qt::ControlModifier) { slotValueInc(10); } else if (e->modifiers() == Qt::AltModifier) { slotValueInc(0.1); } else { slotValueInc(); } } else { if (e->modifiers() == Qt::ControlModifier) { slotValueDec(10); } else if (e->modifiers() == Qt::AltModifier) { slotValueDec(0.1); } else { slotValueDec(); } } e->accept(); } void CustomLabel::slotValueInc(double factor) { setNewValue(value() + m_step * factor, true); } void CustomLabel::slotValueDec(double factor) { setNewValue(value() - m_step * factor, true); } void CustomLabel::setProgressValue(double value) { setValue(qRound(value)); } void CustomLabel::setNewValue(double value, bool update) { setValue(qRound(value)); emit valueChanged(qRound(value), update); } void CustomLabel::setStep(double step) { m_step = step; } void CustomLabel::focusInEvent(QFocusEvent *) { setFocusPolicy(Qt::WheelFocus); } void CustomLabel::focusOutEvent(QFocusEvent *) { setFocusPolicy(Qt::StrongFocus); } diff --git a/src/widgets/dragvalue.h b/src/widgets/dragvalue.h index 8a3d3dcff..f74eca71b 100644 --- a/src/widgets/dragvalue.h +++ b/src/widgets/dragvalue.h @@ -1,169 +1,169 @@ /*************************************************************************** * Copyright (C) 2011 by Till Theato (root@ttill.de) * * 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 DRAGVALUE_H #define DRAGVALUE_H #include #include #include #include #include class QAction; class QMenu; class KSelectAction; class CustomLabel : public QProgressBar { Q_OBJECT public: explicit CustomLabel(const QString &label, bool showSlider = true, int range = 1000, QWidget *parent = nullptr); void setProgressValue(double value); void setStep(double step); protected: // virtual void mouseDoubleClickEvent(QMouseEvent * event); void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; // virtual void paintEvent(QPaintEvent *event); void wheelEvent(QWheelEvent *event) override; void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; private: QPoint m_dragStartPosition; QPoint m_dragLastPosition; bool m_dragMode; bool m_showSlider; double m_step; void slotValueInc(double factor = 1); void slotValueDec(double factor = 1); void setNewValue(double, bool); signals: void valueChanged(double, bool); void setInTimeline(); void resetValue(); }; /** * @brief A widget for modifying numbers by dragging, using the mouse wheel or entering them with the keyboard. */ class DragValue : public QWidget { Q_OBJECT public: /** * @brief Default constructor. * @param label The label that will be displayed in the progress bar * @param defaultValue The default value * @param decimals The number of decimals for the parameter. 0 means it is an integer * @param min The minimum value * @param max The maximum value * @param id Used to identify this widget. If this parameter is set, "Show in Timeline" will be available in context menu. * @param suffix The suffix that will be displayed in the spinbox (for example '%') * @param showSlider If disabled, user can still drag on the label but no progress bar is shown */ explicit DragValue(const QString &label, double defaultValue, int decimals, double min = 0, double max = 100, int id = -1, - const QString &suffix = QString(), bool showSlider = true, QWidget *parent = nullptr); + const QString &suffix = QString(), bool showSlider = true, bool oddOnly = false, QWidget *parent = nullptr); ~DragValue() override; /** @brief Returns the precision = number of decimals */ int precision() const; /** @brief Returns the maximum value */ qreal minimum() const; /** @brief Returns the minimum value */ qreal maximum() const; /** @brief Sets the minimum value. */ void setMinimum(qreal min); /** @brief Sets the maximum value. */ void setMaximum(qreal max); /** @brief Sets minimum and maximum value. */ void setRange(qreal min, qreal max); /** @brief Sets the size of a step (when dragging or using the mouse wheel). */ void setStep(qreal step); /** @brief Returns the current value */ qreal value() const; /** @brief Change the "inTimeline" property to paint the intimeline widget differently. */ void setInTimelineProperty(bool intimeline); /** @brief Returns minimum size for QSpinBox, used to set all spinboxes to the same width. */ int spinSize(); /** @brief Sets the minimum size for QSpinBox, used to set all spinboxes to the same width. */ void setSpinSize(int width); /** @brief Returns true if widget is currently being edited */ bool hasEditFocus() const; public slots: /** @brief Sets the value (forced to be in the valid range) and emits valueChanged. */ void setValue(double value, bool final = true); void setValueFromProgress(double value, bool final); /** @brief Resets to default value */ void slotReset(); signals: void valueChanged(double value, bool final = true); void inTimeline(int); /* * Private */ protected: /*virtual void mousePressEvent(QMouseEvent *e); virtual void mouseMoveEvent(QMouseEvent *e); virtual void mouseReleaseEvent(QMouseEvent *e);*/ /** @brief Forwards tab focus to lineedit since it is disabled. */ void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; // virtual void keyPressEvent(QKeyEvent *e); // virtual void wheelEvent(QWheelEvent *e); // virtual void paintEvent( QPaintEvent * event ); private slots: void slotEditingFinished(); void slotSetScaleMode(int mode); void slotSetDirectUpdate(bool directUpdate); void slotShowContextMenu(const QPoint &pos); void slotSetValue(int value); void slotSetValue(double value); void slotSetInTimeline(); private: double m_maximum; double m_minimum; int m_decimals; double m_default; int m_id; QSpinBox *m_intEdit; QDoubleSpinBox *m_doubleEdit; QMenu *m_menu; KSelectAction *m_scale; QAction *m_directUpdate; CustomLabel *m_label; }; #endif diff --git a/src/widgets/geometrywidget.cpp b/src/widgets/geometrywidget.cpp index d25cf7666..670ffcdff 100644 --- a/src/widgets/geometrywidget.cpp +++ b/src/widgets/geometrywidget.cpp @@ -1,448 +1,448 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle * * 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 "geometrywidget.h" #include "core.h" #include "doublewidget.h" #include "dragvalue.h" #include "monitor/monitor.h" #include #include GeometryWidget::GeometryWidget(Monitor *monitor, QPair range, const QRect &rect, double opacity, const QSize frameSize, bool useRatioLock, bool useOpacity, bool percentOpacity, QWidget *parent) : QWidget(parent) , m_min(range.first) , m_max(range.second) , m_active(false) , m_monitor(monitor) , m_opacity(nullptr) , m_opacityFactor(percentOpacity ? 1. : 100.) { Q_UNUSED(useRatioLock) setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); auto *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); m_defaultSize = pCore->getCurrentFrameSize(); m_sourceSize = frameSize.isValid() ? frameSize : m_defaultSize; /*QString paramName = i18n(paramTag.toUtf8().data()); QString comment = m_model->data(ix, AssetParameterModel::CommentRole).toString(); if (!comment.isEmpty()) { comment = i18n(comment.toUtf8().data()); }*/ auto *horLayout = new QHBoxLayout; horLayout->setSpacing(2); - m_spinX = new DragValue(i18nc("x axis position", "X"), 0, 0, -99000, 99000, -1, QString(), false, this); + m_spinX = new DragValue(i18nc("x axis position", "X"), 0, 0, -99000, 99000, -1, QString(), false, false, this); connect(m_spinX, &DragValue::valueChanged, this, &GeometryWidget::slotAdjustRectKeyframeValue); horLayout->addWidget(m_spinX); - m_spinY = new DragValue(i18nc("y axis position", "Y"), 0, 0, -99000, 99000, -1, QString(), false, this); + m_spinY = new DragValue(i18nc("y axis position", "Y"), 0, 0, -99000, 99000, -1, QString(), false, false, this); connect(m_spinY, &DragValue::valueChanged, this, &GeometryWidget::slotAdjustRectKeyframeValue); horLayout->addWidget(m_spinY); - m_spinWidth = new DragValue(i18nc("Frame width", "W"), m_defaultSize.width(), 0, 1, 99000, -1, QString(), false, this); + m_spinWidth = new DragValue(i18nc("Frame width", "W"), m_defaultSize.width(), 0, 1, 99000, -1, QString(), false, false, this); connect(m_spinWidth, &DragValue::valueChanged, this, &GeometryWidget::slotAdjustRectWidth); horLayout->addWidget(m_spinWidth); // Lock ratio stuff m_lockRatio = new QAction(QIcon::fromTheme(QStringLiteral("link")), i18n("Lock aspect ratio"), this); m_lockRatio->setCheckable(true); connect(m_lockRatio, &QAction::triggered, this, &GeometryWidget::slotLockRatio); auto *ratioButton = new QToolButton; ratioButton->setDefaultAction(m_lockRatio); horLayout->addWidget(ratioButton); - m_spinHeight = new DragValue(i18nc("Frame height", "H"), m_defaultSize.height(), 0, 1, 99000, -1, QString(), false, this); + m_spinHeight = new DragValue(i18nc("Frame height", "H"), m_defaultSize.height(), 0, 1, 99000, -1, QString(), false, false, this); connect(m_spinHeight, &DragValue::valueChanged, this, &GeometryWidget::slotAdjustRectHeight); horLayout->addWidget(m_spinHeight); horLayout->addStretch(10); auto *horLayout2 = new QHBoxLayout; horLayout2->setSpacing(2); - m_spinSize = new DragValue(i18n("Size"), 100, 2, 1, 99000, -1, i18n("%"), false, this); + m_spinSize = new DragValue(i18n("Size"), 100, 2, 1, 99000, -1, i18n("%"), false, false, this); m_spinSize->setStep(5); connect(m_spinSize, &DragValue::valueChanged, this, &GeometryWidget::slotResize); horLayout2->addWidget(m_spinSize); if (useOpacity) { - m_opacity = new DragValue(i18n("Opacity"), 100, 0, 0, 100, -1, i18n("%"), true, this); + m_opacity = new DragValue(i18n("Opacity"), 100, 0, 0, 100, -1, i18n("%"), true, false, this); m_opacity->setValue((int)(opacity * m_opacityFactor)); connect(m_opacity, &DragValue::valueChanged, [&]() { emit valueChanged(getValue()); }); horLayout2->addWidget(m_opacity); } horLayout2->addStretch(10); // Build buttons m_originalSize = new QAction(QIcon::fromTheme(QStringLiteral("zoom-original")), i18n("Adjust to original size"), this); connect(m_originalSize, &QAction::triggered, this, &GeometryWidget::slotAdjustToSource); m_originalSize->setCheckable(true); QAction *adjustSize = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-best")), i18n("Adjust and center in frame"), this); connect(adjustSize, &QAction::triggered, this, &GeometryWidget::slotAdjustToFrameSize); QAction *fitToWidth = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-width")), i18n("Fit to width"), this); connect(fitToWidth, &QAction::triggered, this, &GeometryWidget::slotFitToWidth); QAction *fitToHeight = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-height")), i18n("Fit to height"), this); connect(fitToHeight, &QAction::triggered, this, &GeometryWidget::slotFitToHeight); QAction *alignleft = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-left")), i18n("Align left"), this); connect(alignleft, &QAction::triggered, this, &GeometryWidget::slotMoveLeft); QAction *alignhcenter = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-hor")), i18n("Center horizontally"), this); connect(alignhcenter, &QAction::triggered, this, &GeometryWidget::slotCenterH); QAction *alignright = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-right")), i18n("Align right"), this); connect(alignright, &QAction::triggered, this, &GeometryWidget::slotMoveRight); QAction *aligntop = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-top")), i18n("Align top"), this); connect(aligntop, &QAction::triggered, this, &GeometryWidget::slotMoveTop); QAction *alignvcenter = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-vert")), i18n("Center vertically"), this); connect(alignvcenter, &QAction::triggered, this, &GeometryWidget::slotCenterV); QAction *alignbottom = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-bottom")), i18n("Align bottom"), this); connect(alignbottom, &QAction::triggered, this, &GeometryWidget::slotMoveBottom); auto *alignLayout = new QHBoxLayout; alignLayout->setSpacing(0); auto *alignButton = new QToolButton; alignButton->setDefaultAction(alignleft); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(alignhcenter); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(alignright); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(aligntop); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(alignvcenter); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(alignbottom); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(m_originalSize); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(adjustSize); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(fitToWidth); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignButton = new QToolButton; alignButton->setDefaultAction(fitToHeight); alignButton->setAutoRaise(true); alignLayout->addWidget(alignButton); alignLayout->addStretch(10); layout->addLayout(horLayout); layout->addLayout(alignLayout); layout->addLayout(horLayout2); slotUpdateGeometryRect(rect); adjustSizeValue(); slotAdjustRectKeyframeValue(); setMinimumHeight(m_spinX->minimumHeight() + alignButton->sizeHint().height() + m_spinSize->minimumHeight()); } void GeometryWidget::slotAdjustToSource() { m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); m_spinWidth->setValue((int)(m_sourceSize.width() / pCore->getCurrentSar() + 0.5), false); m_spinHeight->setValue(m_sourceSize.height(), false); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); if (m_lockRatio->isChecked()) { m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), m_originalSize->isChecked() ? (double)m_sourceSize.width() / m_sourceSize.height() : (double)m_defaultSize.width() / m_defaultSize.height()); } } void GeometryWidget::slotAdjustToFrameSize() { double monitorDar = pCore->getCurrentDar(); double sourceDar = m_sourceSize.width() / m_sourceSize.height(); m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); if (sourceDar > monitorDar) { // Fit to width double factor = (double)m_defaultSize.width() / m_sourceSize.width() * pCore->getCurrentSar(); m_spinHeight->setValue((int)(m_sourceSize.height() * factor + 0.5)); m_spinWidth->setValue(m_defaultSize.width()); // Center m_spinY->blockSignals(true); m_spinY->setValue((m_defaultSize.height() - m_spinHeight->value()) / 2); m_spinY->blockSignals(false); } else { // Fit to height double factor = (double)m_defaultSize.height() / m_sourceSize.height(); m_spinHeight->setValue(m_defaultSize.height()); m_spinWidth->setValue((int)(m_sourceSize.width() / pCore->getCurrentSar() * factor + 0.5)); // Center m_spinX->blockSignals(true); m_spinX->setValue((m_defaultSize.width() - m_spinWidth->value()) / 2); m_spinX->blockSignals(false); } m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); } void GeometryWidget::slotFitToWidth() { double factor = (double)m_defaultSize.width() / m_sourceSize.width() * pCore->getCurrentSar(); m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); m_spinHeight->setValue((int)(m_sourceSize.height() * factor + 0.5)); m_spinWidth->setValue(m_defaultSize.width()); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); } void GeometryWidget::slotFitToHeight() { double factor = (double)m_defaultSize.height() / m_sourceSize.height(); m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); m_spinHeight->setValue(m_defaultSize.height()); m_spinWidth->setValue((int)(m_sourceSize.width() / pCore->getCurrentSar() * factor + 0.5)); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); } void GeometryWidget::slotResize(double value) { m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); int w = m_originalSize->isChecked() ? m_sourceSize.width() : m_defaultSize.width(); int h = m_originalSize->isChecked() ? m_sourceSize.height() : m_defaultSize.height(); m_spinWidth->setValue(w * value / 100.0); m_spinHeight->setValue(h * value / 100.0); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); slotAdjustRectKeyframeValue(); } /** @brief Moves the rect to the left frame border (x position = 0). */ void GeometryWidget::slotMoveLeft() { m_spinX->setValue(0); } /** @brief Centers the rect horizontally. */ void GeometryWidget::slotCenterH() { m_spinX->setValue((m_defaultSize.width() - m_spinWidth->value()) / 2); } /** @brief Moves the rect to the right frame border (x position = frame width - rect width). */ void GeometryWidget::slotMoveRight() { m_spinX->setValue(m_defaultSize.width() - m_spinWidth->value()); } /** @brief Moves the rect to the top frame border (y position = 0). */ void GeometryWidget::slotMoveTop() { m_spinY->setValue(0); } /** @brief Centers the rect vertically. */ void GeometryWidget::slotCenterV() { m_spinY->setValue((m_defaultSize.height() - m_spinHeight->value()) / 2); } /** @brief Moves the rect to the bottom frame border (y position = frame height - rect height). */ void GeometryWidget::slotMoveBottom() { m_spinY->setValue(m_defaultSize.height() - m_spinHeight->value()); } /** @brief Un/Lock aspect ratio for size in effect parameter. */ void GeometryWidget::slotLockRatio() { auto *lockRatio = qobject_cast(QObject::sender()); if (lockRatio->isChecked()) { m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), m_originalSize->isChecked() ? (double)m_sourceSize.width() / m_sourceSize.height() : (double)m_defaultSize.width() / m_defaultSize.height()); } else { m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), -1); } } void GeometryWidget::slotAdjustRectHeight() { if (m_lockRatio->isChecked()) { m_spinWidth->blockSignals(true); if (m_originalSize->isChecked()) { m_spinWidth->setValue((int)(m_spinHeight->value() * m_sourceSize.width() / m_sourceSize.height() + 0.5)); } else { m_spinWidth->setValue((int)(m_spinHeight->value() * m_defaultSize.width() / m_defaultSize.height() + 0.5)); } m_spinWidth->blockSignals(false); } adjustSizeValue(); slotAdjustRectKeyframeValue(); } void GeometryWidget::slotAdjustRectWidth() { if (m_lockRatio->isChecked()) { m_spinHeight->blockSignals(true); if (m_originalSize->isChecked()) { m_spinHeight->setValue((int)(m_spinWidth->value() * m_sourceSize.height() / m_sourceSize.width() + 0.5)); } else { m_spinHeight->setValue((int)(m_spinWidth->value() * m_defaultSize.height() / m_defaultSize.width() + 0.5)); } m_spinHeight->blockSignals(false); } adjustSizeValue(); slotAdjustRectKeyframeValue(); } void GeometryWidget::adjustSizeValue() { double size; if ((double)m_spinWidth->value() / m_spinHeight->value() < pCore->getCurrentDar()) { if (m_originalSize->isChecked()) { size = m_spinWidth->value() * 100.0 / m_sourceSize.width(); } else { size = m_spinWidth->value() * 100.0 / m_defaultSize.width(); } } else { if (m_originalSize->isChecked()) { size = m_spinHeight->value() * 100.0 / m_sourceSize.height(); } else { size = m_spinHeight->value() * 100.0 / m_defaultSize.height(); } } m_spinSize->blockSignals(true); m_spinSize->setValue(size); m_spinSize->blockSignals(false); } void GeometryWidget::slotAdjustRectKeyframeValue() { QRect rect(m_spinX->value(), m_spinY->value(), m_spinWidth->value(), m_spinHeight->value()); m_monitor->setUpEffectGeometry(rect); emit valueChanged(getValue()); } void GeometryWidget::slotUpdateGeometryRect(const QRect r) { if (!r.isValid()) { return; } m_spinX->blockSignals(true); m_spinY->blockSignals(true); m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); m_spinX->setValue(r.x()); m_spinY->setValue(r.y()); m_spinWidth->setValue(r.width()); m_spinHeight->setValue(r.height()); m_spinX->blockSignals(false); m_spinY->blockSignals(false); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); m_monitor->setUpEffectGeometry(r); emit valueChanged(getValue()); } void GeometryWidget::setValue(const QRect r, double opacity) { if (!r.isValid()) { return; } m_spinX->blockSignals(true); m_spinY->blockSignals(true); m_spinWidth->blockSignals(true); m_spinHeight->blockSignals(true); m_spinX->setValue(r.x()); m_spinY->setValue(r.y()); m_spinWidth->setValue(r.width()); m_spinHeight->setValue(r.height()); if (m_opacity) { m_opacity->blockSignals(true); if (opacity < 0) { opacity = 100 / m_opacityFactor; } m_opacity->setValue((int)(opacity * m_opacityFactor + 0.5)); m_opacity->blockSignals(false); } m_spinX->blockSignals(false); m_spinY->blockSignals(false); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); adjustSizeValue(); m_monitor->setUpEffectGeometry(r); } const QString GeometryWidget::getValue() const { if (m_opacity) { QLocale locale; return QStringLiteral("%1 %2 %3 %4 %5") .arg(m_spinX->value()) .arg(m_spinY->value()) .arg(m_spinWidth->value()) .arg(m_spinHeight->value()) .arg(locale.toString(m_opacity->value() / m_opacityFactor)); } return QStringLiteral("%1 %2 %3 %4").arg(m_spinX->value()).arg(m_spinY->value()).arg(m_spinWidth->value()).arg(m_spinHeight->value()); } void GeometryWidget::connectMonitor(bool activate) { if (m_active == activate) { return; } m_active = activate; if (activate) { connect(m_monitor, &Monitor::effectChanged, this, &GeometryWidget::slotUpdateGeometryRect, Qt::UniqueConnection); QRect rect(m_spinX->value(), m_spinY->value(), m_spinWidth->value(), m_spinHeight->value()); m_monitor->setUpEffectGeometry(rect); } else { m_monitor->setEffectKeyframe(false); disconnect(m_monitor, &Monitor::effectChanged, this, &GeometryWidget::slotUpdateGeometryRect); } } void GeometryWidget::slotSetRange(QPair range) { m_min = range.first; m_max = range.second; }